# Common Errors and Solutions This guide covers the most frequently encountered errors in Changemaker Lite V2 and their solutions. ## Overview ### How to Use This Guide 1. **Find your error** - Use the error code or message to locate the section 2. **Diagnose** - Read the symptoms and causes 3. **Apply solution** - Follow step-by-step instructions 4. **Prevent recurrence** - Implement preventive measures ### Error Severity Levels | Level | Icon | Meaning | Action | |-------|------|---------|--------| | **Critical** | 🔴 | System down or data at risk | Fix immediately | | **High** | 🟠 | Feature unavailable | Fix within hours | | **Medium** | 🟡 | Degraded performance | Fix within days | | **Low** | 🟢 | Minor inconvenience | Fix when convenient | ### Quick Error Lookup | Error Code | Category | Page | |------------|----------|------| | 401 | Authentication | [Link](#401-unauthorized) | | 403 | Authorization | [Link](#403-forbidden) | | 404 | Not Found | [Link](#404-not-found) | | 422 | Validation | [Link](#422-unprocessable-entity) | | 500 | Server Error | [Link](#500-internal-server-error) | | CORS | Frontend | [Link](#cors-errors) | | ECONNREFUSED | Database | [Link](#connection-refused) | --- ## Authentication Errors ### 401 Unauthorized **Severity:** 🟠 High #### Symptoms ```json { "error": "Unauthorized", "message": "Invalid or missing token" } ``` Browser console: ``` Error: Request failed with status code 401 ``` #### Common Causes 1. **Missing token** - No Authorization header sent 2. **Expired token** - Access token older than 15 minutes 3. **Invalid token** - Corrupted or tampered token 4. **Wrong environment** - Token from dev used in production #### Solutions **Solution 1: Check if logged in** ```typescript // In browser console console.log(localStorage.getItem('auth-storage')); ``` If null or missing `accessToken`, you need to log in again. **Solution 2: Refresh token** The frontend automatically refreshes tokens. If this fails: 1. Log out completely 2. Clear localStorage: `localStorage.clear()` 3. Log in again **Solution 3: Verify API configuration** Check `admin/.env`: ```bash VITE_API_URL=http://localhost:4000 # Must match actual API URL ``` **Solution 4: Check token expiration** ```typescript // In browser console const storage = JSON.parse(localStorage.getItem('auth-storage')); const payload = JSON.parse(atob(storage.state.accessToken.split('.')[1])); console.log('Token expires:', new Date(payload.exp * 1000)); console.log('Current time:', new Date()); ``` If expired, the refresh interceptor should handle this. If not working: ```bash # Check API logs docker compose logs api | grep "refresh" ``` #### Prevention - **Auto-refresh works** - Frontend handles token refresh automatically - **Long sessions** - Refresh tokens valid for 7 days - **Activity-based** - Tokens refresh on API calls - **Clear error handling** - Frontend redirects to login on failure !!! warning "Security Note" 401 errors may return generic messages to prevent user enumeration. This is intentional security behavior. --- ### 403 Forbidden **Severity:** 🟠 High #### Symptoms ```json { "error": "Forbidden", "message": "Insufficient permissions" } ``` Or role-specific: ```json { "error": "Forbidden", "message": "Requires one of: SUPER_ADMIN, MAP_ADMIN" } ``` #### Common Causes 1. **Wrong role** - User lacks required role 2. **TEMP user** - Temporary users restricted from most features 3. **Feature disabled** - Feature flag not enabled 4. **Wrong endpoint** - Using admin endpoint as public user #### Solutions **Solution 1: Check user role** ```bash # In database docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT email, role FROM \"User\" WHERE email = 'your@email.com';" ``` **Solution 2: Update user role** ```sql -- Via Prisma Studio (recommended) docker compose exec api npx prisma studio -- Navigate to User table, edit role -- Or via SQL docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "UPDATE \"User\" SET role = 'MAP_ADMIN' WHERE email = 'your@email.com';" ``` **Solution 3: Check feature flags** ```bash # In API logs docker compose logs api | grep "ENABLE_" # Check .env cat .env | grep ENABLE ``` **Solution 4: Verify endpoint permissions** Check `api/src/modules/*/routes.ts`: ```typescript // Admin endpoint router.post('/', authenticate, requireRole('SUPER_ADMIN'), ...); // Public endpoint (no auth) router.get('/public', ...); ``` #### Prevention - **Role-based access control** - Clear role hierarchy - **Explicit permissions** - Each endpoint lists required roles - **Audit trail** - Track permission changes - **Documentation** - Role matrix in [Access Control](../technical/api-reference.md#access-control) --- ### Invalid Token **Severity:** 🟠 High #### Symptoms ```json { "error": "Unauthorized", "message": "Invalid token" } ``` Or in API logs: ``` Error: jwt malformed Error: invalid signature Error: jwt must be provided ``` #### Common Causes 1. **Corrupted token** - LocalStorage corruption 2. **Wrong secret** - JWT_ACCESS_SECRET changed 3. **Modified token** - Attempted tampering 4. **Format error** - Not a valid JWT structure #### Solutions **Solution 1: Clear and re-login** ```javascript // In browser console localStorage.clear(); // Then log in again ``` **Solution 2: Verify JWT structure** Valid JWT has 3 parts separated by dots: ```javascript const token = 'header.payload.signature'; console.log(token.split('.').length); // Should be 3 ``` **Solution 3: Check secret configuration** ```bash # In .env JWT_ACCESS_SECRET=your-secret-here-32-chars-min JWT_REFRESH_SECRET=different-secret-here-32-chars-min # Secrets must: # - Be different from each other # - Be at least 32 characters # - Remain unchanged (changing invalidates all tokens) ``` **Solution 4: Verify token in logs** ```bash # API logs show token validation errors docker compose logs api | tail -100 | grep "jwt" ``` #### Prevention - **Secure secrets** - Use `openssl rand -hex 32` - **Never commit secrets** - Keep in .env (gitignored) - **Rotate carefully** - Changing secrets logs out all users - **Monitor errors** - Alert on spike in invalid token errors --- ### Token Expired **Severity:** 🟡 Medium #### Symptoms ```json { "error": "Unauthorized", "message": "Token expired" } ``` Or: ``` Error: jwt expired ``` #### Common Causes 1. **Access token expired** - Normal after 15 minutes of inactivity 2. **Refresh token expired** - Refresh token older than 7 days 3. **System clock skew** - Server/client time mismatch 4. **Refresh failed** - Refresh token invalid or revoked #### Solutions **Solution 1: Automatic refresh** Frontend automatically refreshes tokens on 401. If this fails: ```javascript // Check refresh token in localStorage const storage = JSON.parse(localStorage.getItem('auth-storage')); console.log('Has refresh token:', !!storage?.state?.refreshToken); ``` **Solution 2: Manual login** If refresh token expired (after 7 days): 1. You'll be redirected to login automatically 2. Log in with email/password 3. New tokens issued **Solution 3: Check system time** ```bash # On server date # Sync if incorrect sudo ntpdate -s time.nist.gov ``` **Solution 4: Verify token expiration** ```bash # In API logs docker compose logs api | grep "expired" # Check token age docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT email, \"createdAt\", \"expiresAt\" FROM \"RefreshToken\" ORDER BY \"createdAt\" DESC LIMIT 10;" ``` #### Prevention - **Sliding sessions** - Tokens auto-refresh on activity - **Long refresh window** - 7-day refresh token validity - **Graceful handling** - Automatic re-login redirect - **Activity tracking** - Monitor token refresh patterns !!! tip "Developer Tip" During development, use longer token expiration in .env: ```bash JWT_ACCESS_EXPIRATION=1d # Instead of 15m JWT_REFRESH_EXPIRATION=30d # Instead of 7d ``` --- ### User Not Found **Severity:** 🟡 Medium #### Symptoms ```json { "error": "Unauthorized", "message": "Invalid credentials" } ``` Note: Same message for both "user not found" and "wrong password" (security feature). #### Common Causes 1. **Wrong email** - Typo in email address 2. **User deleted** - Account removed from database 3. **Wrong database** - Connected to wrong environment 4. **Case sensitivity** - Email stored differently #### Solutions **Solution 1: Verify user exists** ```bash # Check database docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT email, role FROM \"User\" WHERE email ILIKE '%search%';" ``` **Solution 2: Check email format** Emails are stored lowercase: ```sql -- Find user case-insensitive SELECT * FROM "User" WHERE LOWER(email) = LOWER('User@Example.com'); ``` **Solution 3: Create user if missing** ```bash # Via API curl -X POST http://localhost:4000/api/auth/register \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "SecurePass123!", "name": "User Name" }' # Or via admin UI at /app/users ``` **Solution 4: Check database connection** ```bash # Verify correct database docker compose exec api npx prisma db pull # Check DATABASE_URL in .env cat .env | grep DATABASE_URL ``` #### Prevention - **Email validation** - Enforce valid email format - **Case normalization** - Store emails lowercase - **Soft deletes** - Consider flagging instead of deleting - **Audit trail** - Log user deletions --- ## API Errors ### 500 Internal Server Error **Severity:** 🔴 Critical #### Symptoms ```json { "error": "Internal Server Error", "message": "An unexpected error occurred" } ``` Or frontend error: ``` Error: Request failed with status code 500 ``` #### Common Causes 1. **Unhandled exception** - Code threw unexpected error 2. **Database error** - Query failed 3. **Missing environment variable** - Required config missing 4. **Type error** - Runtime type mismatch #### Solutions **Solution 1: Check API logs** ```bash # View recent logs docker compose logs api --tail=100 # Follow logs in real-time docker compose logs -f api # Search for errors docker compose logs api | grep -i error | tail -20 ``` **Solution 2: Common error patterns** ```javascript // Missing environment variable Error: SMTP_HOST is required // Solution: Add to .env // Database connection error Error: Can't reach database server at `v2-postgres:5432` // Solution: Check database is running // Type error TypeError: Cannot read property 'id' of undefined // Solution: Check code for null checks ``` **Solution 3: Restart API** ```bash # Restart API container docker compose restart api # Or rebuild if code changed docker compose up -d --build api ``` **Solution 4: Enable debug logging** ```bash # In .env LOG_LEVEL=debug # Restart API docker compose restart api # Check detailed logs docker compose logs api ``` #### Prevention - **Error handling** - Try/catch in all routes - **Input validation** - Validate all inputs with Zod - **Type safety** - Use TypeScript strictly - **Health checks** - Monitor API health - **Alerting** - Set up alerts for 500 errors !!! warning "Production Alert" 500 errors indicate bugs. Always investigate and fix root cause. --- ### 400 Bad Request **Severity:** 🟡 Medium #### Symptoms ```json { "error": "Bad Request", "message": "Invalid request format" } ``` Or with validation details: ```json { "error": "Bad Request", "message": "Validation failed: 2 errors" } ``` #### Common Causes 1. **Invalid JSON** - Malformed request body 2. **Wrong Content-Type** - Missing or incorrect header 3. **Missing required field** - Required parameter not sent 4. **Invalid data type** - String sent for number field #### Solutions **Solution 1: Check request format** ```javascript // Correct format const response = await api.post('/api/users', { email: 'user@example.com', password: 'SecurePass123!', name: 'User Name' }, { headers: { 'Content-Type': 'application/json' } }); // Common mistakes: // ❌ Missing Content-Type header // ❌ Sending FormData to JSON endpoint // ❌ Malformed JSON (trailing comma, unquoted keys) ``` **Solution 2: Validate against schema** Check API schema in `api/src/modules/*/schemas.ts`: ```typescript // Example: User creation schema export const createUserSchema = z.object({ email: z.string().email(), password: z.string().min(12), name: z.string().min(1), role: z.enum(['USER', 'MAP_ADMIN', 'INFLUENCE_ADMIN', 'SUPER_ADMIN']).optional() }); ``` **Solution 3: Check API logs for details** ```bash # Logs show validation errors docker compose logs api | grep "Validation failed" # Example output: # Validation failed: { # "email": "Invalid email format", # "password": "Must be at least 12 characters" # } ``` **Solution 4: Test with curl** ```bash # Test request curl -X POST http://localhost:4000/api/users \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "email": "test@example.com", "password": "SecurePass123!", "name": "Test User" }' ``` #### Prevention - **Client-side validation** - Validate before sending - **TypeScript types** - Use generated types from API - **Schema documentation** - Document all endpoints - **Error messages** - Clear validation error messages --- ### 404 Not Found **Severity:** 🟢 Low to 🟡 Medium #### Symptoms ```json { "error": "Not Found", "message": "Resource not found" } ``` Or specific: ```json { "error": "Not Found", "message": "Campaign not found" } ``` #### Common Causes 1. **Wrong ID** - Resource doesn't exist 2. **Wrong URL** - Typo in endpoint path 3. **Deleted resource** - Resource was deleted 4. **Wrong HTTP method** - GET instead of POST #### Solutions **Solution 1: Verify resource exists** ```bash # Check database docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, name FROM \"Campaign\" WHERE id = 'YOUR_ID';" ``` **Solution 2: Check URL format** ```javascript // Correct formats GET /api/campaigns/:id // Single campaign GET /api/campaigns // List campaigns POST /api/campaigns // Create campaign PUT /api/campaigns/:id // Update campaign DELETE /api/campaigns/:id // Delete campaign // Common mistakes: // ❌ /api/campaign/:id (singular, should be plural) // ❌ /api/campaigns/id/:id (extra 'id/' in path) // ❌ /api/campaign (wrong singular/plural) ``` **Solution 3: Check route registration** ```bash # API logs show registered routes on startup docker compose logs api | grep "Registered route" # Or check routes file cat api/src/modules/*/routes.ts ``` **Solution 4: Test endpoint** ```bash # List all campaigns to verify endpoint curl http://localhost:4000/api/campaigns # Test specific ID curl http://localhost:4000/api/campaigns/YOUR_ID ``` #### Prevention - **UUID validation** - Validate ID format before querying - **Soft deletes** - Flag as deleted instead of removing - **Resource existence checks** - Verify before operations - **Clear error messages** - Specify which resource not found --- ### 422 Unprocessable Entity **Severity:** 🟡 Medium #### Symptoms ```json { "error": "Unprocessable Entity", "message": "Validation failed", "details": { "email": "Email already exists", "password": "Must contain uppercase, lowercase, and digit" } } ``` #### Common Causes 1. **Business logic violation** - Email already exists 2. **Data integrity** - Foreign key doesn't exist 3. **Complex validation** - Password requirements not met 4. **State conflict** - Can't delete resource in use #### Solutions **Solution 1: Read validation details** The `details` field shows exactly what's wrong: ```javascript try { await api.post('/api/users', userData); } catch (error) { if (error.response?.status === 422) { console.log('Validation errors:', error.response.data.details); // Show to user field-by-field } } ``` **Solution 2: Check constraints** ```bash # Email uniqueness docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT email FROM \"User\" WHERE email = 'test@example.com';" # Foreign key exists docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id FROM \"Campaign\" WHERE id = 'CAMPAIGN_ID';" ``` **Solution 3: Fix data** Common fixes: ```javascript // Email already exists → Use different email email: 'newuser@example.com' // Password too weak → Meet requirements password: 'SecurePass123!' // 12+ chars, upper, lower, digit // Foreign key missing → Create parent first // Create campaign before creating email // Resource in use → Delete dependents first // Delete locations before deleting cut ``` **Solution 4: Check database schema** ```bash # View constraints docker compose exec api npx prisma studio # Navigate to model, see unique fields and relations ``` #### Prevention - **Client validation** - Check constraints before submitting - **Clear requirements** - Document validation rules - **Helpful messages** - Explain how to fix - **Cascade deletes** - Auto-delete dependents when safe --- ## Database Errors ### Connection Refused **Severity:** 🔴 Critical #### Symptoms ``` Error: connect ECONNREFUSED 127.0.0.1:5433 ``` Or: ``` Error: Can't reach database server at `v2-postgres:5432` ``` #### Common Causes 1. **Database not running** - Container stopped 2. **Wrong connection string** - Incorrect host/port 3. **Network issue** - Container can't reach database 4. **Port conflict** - Port already in use #### Solutions **Solution 1: Check database status** ```bash # List running containers docker compose ps # Database should show as "running" # If not: docker compose up -d v2-postgres ``` **Solution 2: Verify connection string** ```bash # Check .env cat .env | grep DATABASE_URL # Should be (from API container): DATABASE_URL="postgresql://changemaker:PASSWORD@v2-postgres:5432/changemaker_v2" # Or (from host): DATABASE_URL="postgresql://changemaker:PASSWORD@localhost:5433/changemaker_v2" ``` **Solution 3: Check database logs** ```bash # View database logs docker compose logs v2-postgres # Look for: # - "database system is ready to accept connections" (good) # - "FATAL: password authentication failed" (bad - wrong password) # - "port 5432 already in use" (bad - port conflict) ``` **Solution 4: Test connection manually** ```bash # From host docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 -c "SELECT NOW();" # Should return current timestamp # If fails, database isn't running properly ``` **Solution 5: Restart database** ```bash # Restart database container docker compose restart v2-postgres # Or recreate if corrupted docker compose down v2-postgres docker compose up -d v2-postgres # Wait for "ready to accept connections" message docker compose logs -f v2-postgres ``` #### Prevention - **Health checks** - Monitor database availability - **Auto-restart** - Configure restart policy - **Connection pooling** - Handle transient failures - **Alerting** - Alert on connection failures --- ### Too Many Connections **Severity:** 🟠 High #### Symptoms ``` Error: too many connections for database "changemaker_v2" ``` Or: ``` Error: Prepared statement "prisma_xxx" already exists ``` #### Common Causes 1. **Connection leak** - Connections not released 2. **Pool too small** - Not enough connections for load 3. **Long-running queries** - Blocking connections 4. **Multiple clients** - Too many Prisma instances #### Solutions **Solution 1: Check active connections** ```bash # View connections docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT count(*) FROM pg_stat_activity WHERE datname = 'changemaker_v2';" # PostgreSQL default max: 100 connections # Prisma default pool: 10 connections ``` **Solution 2: Kill idle connections** ```sql -- Find idle connections SELECT pid, usename, state, query_start FROM pg_stat_activity WHERE datname = 'changemaker_v2' AND state = 'idle'; -- Kill specific connection SELECT pg_terminate_backend(PID_HERE); -- Kill all idle connections (careful!) SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'changemaker_v2' AND state = 'idle'; ``` **Solution 3: Adjust connection pool** In `api/prisma/schema.prisma`: ```prisma datasource db { provider = "postgresql" url = env("DATABASE_URL") // Add connection pool config // connectionLimit = 10 // Default } ``` Or via DATABASE_URL: ```bash DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=20" ``` **Solution 4: Restart API** ```bash # Restart releases all connections docker compose restart api # Check if connections cleared docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT count(*) FROM pg_stat_activity WHERE datname = 'changemaker_v2';" ``` **Solution 5: Increase PostgreSQL max connections** In `docker-compose.yml`: ```yaml v2-postgres: # ... command: postgres -c max_connections=200 ``` Then restart: ```bash docker compose up -d v2-postgres ``` #### Prevention - **Proper cleanup** - Always close Prisma clients - **Connection pooling** - Use appropriate pool size - **Monitor connections** - Alert on high usage - **Query optimization** - Reduce long-running queries --- ### Unique Constraint Violation **Severity:** 🟡 Medium #### Symptoms ``` Error: Unique constraint failed on the fields: (`email`) ``` Or: ``` PrismaClientKnownRequestError: Unique constraint failed on the constraint: `User_email_key` ``` #### Common Causes 1. **Duplicate email** - User already exists 2. **Race condition** - Two creates at same time 3. **Case sensitivity** - Email differs only in case 4. **Retry logic** - Request sent multiple times #### Solutions **Solution 1: Check existing records** ```bash # Find duplicate docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, email, \"createdAt\" FROM \"User\" WHERE email = 'duplicate@example.com';" ``` **Solution 2: Update instead of create** ```javascript // Instead of: await prisma.user.create({ data: { email, ... } }); // Use upsert: await prisma.user.upsert({ where: { email }, update: { name, ... }, create: { email, name, ... } }); ``` **Solution 3: Handle error gracefully** ```javascript try { await prisma.user.create({ data }); } catch (error) { if (error.code === 'P2002') { // Unique constraint violation const field = error.meta?.target?.[0]; throw new Error(`${field} already exists`); } throw error; } ``` **Solution 4: Delete duplicate** ```bash # If truly duplicate, delete one docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "DELETE FROM \"User\" WHERE id = 'ID_TO_DELETE';" ``` #### Prevention - **Check before create** - Query first to check existence - **Use upsert** - Update or create atomically - **Unique indexes** - Database enforces uniqueness - **Case normalization** - Store emails lowercase --- ### Foreign Key Constraint **Severity:** 🟡 Medium #### Symptoms ``` Error: Foreign key constraint failed on the field: `campaignId` ``` Or: ``` Error: An operation failed because it depends on one or more records that were required but not found. Record to update not found. ``` #### Common Causes 1. **Parent doesn't exist** - Referenced record missing 2. **Wrong ID** - Typo in foreign key value 3. **Delete order** - Trying to delete parent before children 4. **Null constraint** - Foreign key required but null provided #### Solutions **Solution 1: Verify parent exists** ```bash # Check campaign exists docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, name FROM \"Campaign\" WHERE id = 'CAMPAIGN_ID';" ``` **Solution 2: Create parent first** ```javascript // Create campaign first const campaign = await prisma.campaign.create({ data: { name: 'My Campaign', ... } }); // Then create email with campaignId const email = await prisma.campaignEmail.create({ data: { campaignId: campaign.id, // Use created campaign's ID ... } }); ``` **Solution 3: Delete children first** ```javascript // Delete all emails in campaign await prisma.campaignEmail.deleteMany({ where: { campaignId } }); // Then delete campaign await prisma.campaign.delete({ where: { id: campaignId } }); // Or use cascade delete in schema: // @@relation(onDelete: Cascade) ``` **Solution 4: Use transactions** ```javascript // Ensure atomicity await prisma.$transaction([ prisma.campaignEmail.deleteMany({ where: { campaignId } }), prisma.campaign.delete({ where: { id: campaignId } }) ]); ``` #### Prevention - **Cascade deletes** - Configure in schema where appropriate - **Soft deletes** - Flag as deleted instead of removing - **Validation** - Check foreign keys exist before creating - **Transactions** - Use for multi-step operations --- ## Frontend Errors ### Network Error **Severity:** 🟠 High #### Symptoms Browser console: ``` Error: Network Error ``` Or: ``` AxiosError: Request failed with status code undefined ``` User sees: API request fails, loading spinner never stops. #### Common Causes 1. **API down** - API container not running 2. **Wrong API URL** - VITE_API_URL misconfigured 3. **CORS issue** - Browser blocking request 4. **Network timeout** - Request taking too long #### Solutions **Solution 1: Check API status** ```bash # Is API running? docker compose ps api # Check API logs docker compose logs api --tail=50 # Test API directly curl http://localhost:4000/api/health ``` **Solution 2: Verify API URL** ```bash # Check admin .env cat admin/.env # Should have: VITE_API_URL=http://localhost:4000 # In Docker, use: VITE_API_URL=http://api:4000 ``` **Solution 3: Check browser console** Press F12, check: - **Network tab** - Does request appear? What's the status? - **Console tab** - Any CORS errors? **Solution 4: Test from different client** ```bash # From command line curl http://localhost:4000/api/campaigns # If this works but browser doesn't, it's a CORS issue ``` #### Prevention - **Health checks** - Monitor API availability - **Error boundaries** - Catch and display network errors - **Retry logic** - Auto-retry failed requests - **Offline detection** - Detect and handle offline state --- ### CORS Errors **Severity:** 🟠 High #### Symptoms Browser console: ``` Access to XMLHttpRequest at 'http://localhost:4000/api/users' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ``` #### Common Causes 1. **Missing CORS config** - API not configured for CORS 2. **Wrong origin** - Admin URL not in allowed origins 3. **Credentials flag** - withCredentials set but not allowed 4. **Preflight failure** - OPTIONS request failing #### Solutions **Solution 1: Check API CORS configuration** In `api/src/server.ts`: ```typescript app.use(cors({ origin: process.env.CORS_ORIGINS?.split(',') || ['http://localhost:3000'], credentials: true })); ``` **Solution 2: Verify CORS_ORIGINS** ```bash # Check .env cat .env | grep CORS_ORIGINS # Should include admin URL: CORS_ORIGINS=http://localhost:3000,https://app.cmlite.org ``` **Solution 3: Add origin temporarily** For development: ```bash # In .env CORS_ORIGINS=* # Allow all origins (dev only!) # Restart API docker compose restart api ``` **Solution 4: Check preflight request** In browser Network tab: 1. Find OPTIONS request before actual request 2. Check if it returns 200 OK 3. Check response headers include: - Access-Control-Allow-Origin - Access-Control-Allow-Methods - Access-Control-Allow-Headers #### Prevention - **Explicit origins** - List all allowed origins - **Environment-based** - Different origins per environment - **Credentials support** - Enable if using cookies/auth - **Preflight caching** - Cache OPTIONS responses !!! warning "Security Note" Never use `CORS_ORIGINS=*` in production with credentials enabled. --- ### Module Not Found **Severity:** 🟡 Medium #### Symptoms ``` Error: Cannot find module '@/components/MyComponent' ``` Or: ``` Module not found: Can't resolve 'some-package' ``` #### Common Causes 1. **Missing dependency** - Package not installed 2. **Wrong import path** - Typo in path 3. **Path alias issue** - @ alias not configured 4. **Case sensitivity** - Wrong case in filename #### Solutions **Solution 1: Install missing package** ```bash cd admin # Install package npm install some-package # Or if dev dependency npm install -D some-package # Restart dev server npm run dev ``` **Solution 2: Check import path** ```typescript // Wrong: import MyComponent from '@/Component/MyComponent'; // Right: import MyComponent from '@/components/MyComponent'; // Verify file exists: // admin/src/components/MyComponent.tsx ``` **Solution 3: Verify path alias** In `admin/vite.config.ts`: ```typescript export default defineConfig({ resolve: { alias: { '@': path.resolve(__dirname, './src') } } }); ``` In `admin/tsconfig.json`: ```json { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } } ``` **Solution 4: Clear cache and reinstall** ```bash cd admin # Remove node_modules and lock file rm -rf node_modules package-lock.json # Reinstall npm install # Restart npm run dev ``` #### Prevention - **Type checking** - Use TypeScript for import validation - **IDE support** - Configure path aliases in IDE - **Linting** - Use ESLint with import plugin - **Documentation** - Document custom path aliases --- ### Hydration Errors **Severity:** 🟡 Medium #### Symptoms Browser console: ``` Warning: Text content did not match. Server: "..." Client: "..." ``` Or: ``` Error: Hydration failed because the initial UI does not match what was rendered on the server. ``` #### Common Causes 1. **Date formatting** - Server/client timezone difference 2. **Random values** - Using Math.random() or uuid 3. **localStorage** - Reading from localStorage during render 4. **User agent** - Checking window.navigator during SSR 5. **Third-party scripts** - Injected by browser extensions #### Solutions **Solution 1: Use useEffect for client-only code** ```typescript // Wrong: const Component = () => { const value = localStorage.getItem('key'); return
{{message}}
EOF' ``` **Solution 4: Use email template system** ```bash # Navigate to admin UI http://localhost:3000/app/email-templates # Create template there (saved to database + file) ``` #### Prevention - **Seed templates** - Include in database seed - **Template management** - Use admin UI to manage - **Version control** - Keep templates in git - **Validation** - Check template exists before sending --- ### Variable Missing **Severity:** 🟡 Medium #### Symptoms Email received with placeholders not replaced: ``` Hello {{name}}, Your campaign {{campaignName}} is ready. ``` Or API logs: ``` Warning: Template variable 'campaignName' not provided ``` #### Common Causes 1. **Variable not passed** - Missing from variables object 2. **Variable name mismatch** - Typo in variable name 3. **Wrong template** - Using wrong template 4. **Case sensitivity** - Variable name case mismatch #### Solutions **Solution 1: Check template variables** In template file: ```htmlYour campaign "{{campaignName}}" is ready.
Visit: {{campaignUrl}}
``` **Solution 2: Provide all variables** ```typescript await emailService.sendEmail({ to: email, subject: 'Campaign Ready', template: 'campaign-email', variables: { firstName: user.name.split(' ')[0], campaignName: campaign.name, campaignUrl: `${process.env.PUBLIC_URL}/campaigns/${campaign.id}` } }); ``` **Solution 3: Use default values** ```html