diff --git a/README.md b/README.md index 9e4e57a..d29990c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Admin dashboard for Yaltopia Ticket management system built with React, TypeScript, and Vite. +> 📚 **For detailed documentation, see [dev-docs/](./dev-docs/README.md)** + ## Features - User Management diff --git a/dev-docs/API_GUIDE.md b/dev-docs/API_GUIDE.md new file mode 100644 index 0000000..e446186 --- /dev/null +++ b/dev-docs/API_GUIDE.md @@ -0,0 +1,983 @@ +# API & Service Layer Guide + +Complete guide for making API calls in the Yaltopia Ticket Admin application. + +## Table of Contents + +- [Architecture Overview](#architecture-overview) +- [Available Services](#available-services) +- [Basic Usage](#basic-usage) +- [Common Patterns](#common-patterns) +- [Service Methods Reference](#service-methods-reference) +- [Error Handling](#error-handling) +- [Best Practices](#best-practices) +- [Examples](#examples) + +--- + +## Architecture Overview + +### The Service Layer Pattern + +All API calls flow through a centralized service layer: + +``` +┌─────────────────┐ +│ Component │ "I need user data" +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│ Service Layer │ userService.getUsers() +│ (Business │ • Type-safe methods +│ Logic) │ • Error handling +└────────┬────────┘ • Data transformation + │ + ▼ +┌─────────────────┐ +│ API Client │ axios.get('/admin/users') +│ (HTTP Layer) │ • Auth token injection +└────────┬────────┘ • Token refresh + │ • Request/response interceptors + ▼ +┌─────────────────┐ +│ Backend API │ Returns JSON data +└─────────────────┘ +``` + +### Why Service Layer? + +**Before (Bad):** +```typescript +// Direct API calls - scattered everywhere +const response = await axios.get('/api/users') +const users = response.data + +// Different patterns in different files +fetch('/api/users').then(r => r.json()) +``` + +**After (Good):** +```typescript +// Centralized, typed, consistent +import { userService } from '@/services' +const users = await userService.getUsers() +``` + +**Benefits:** +- ✅ Single source of truth +- ✅ Type safety (TypeScript) +- ✅ Automatic authentication +- ✅ Consistent error handling +- ✅ Easy to test +- ✅ Easy to maintain + +--- + +## Available Services + +### Import All Services + +```typescript +import { + authService, // Authentication & authorization + userService, // User management + analyticsService, // Dashboard analytics & metrics + securityService, // Security monitoring & logs + systemService, // System health & maintenance + announcementService,// Announcements management + auditService, // Audit logs & history + settingsService // System settings +} from '@/services' +``` + +### Service Locations + +``` +src/services/ +├── index.ts # Central export (import from here) +├── api/ +│ └── client.ts # Shared axios instance +├── auth.service.ts # authService +├── user.service.ts # userService +├── analytics.service.ts # analyticsService +├── security.service.ts # securityService +├── system.service.ts # systemService +├── announcement.service.ts # announcementService +├── audit.service.ts # auditService +└── settings.service.ts # settingsService +``` + +--- + +## Basic Usage + +### 1. Simple API Call + +```typescript +import { userService } from '@/services' + +// Async/await +async function loadUsers() { + const users = await userService.getUsers() + console.log(users) // Typed response +} + +// Promise +userService.getUsers() + .then(users => console.log(users)) + .catch(error => console.error(error)) +``` + +### 2. With React Query (Recommended) + +```typescript +import { useQuery } from '@tanstack/react-query' +import { userService } from '@/services' + +function UsersPage() { + const { data, isLoading, error } = useQuery({ + queryKey: ['users'], + queryFn: () => userService.getUsers() + }) + + if (isLoading) return
Loading...
+ if (error) return
Error: {error.message}
+ + return ( +
+ {data?.data.map(user => ( +
{user.email}
+ ))} +
+ ) +} +``` + +### 3. With Parameters + +```typescript +import { userService } from '@/services' + +// Fetch with filters +const users = await userService.getUsers({ + page: 1, + limit: 20, + search: 'john', + role: 'ADMIN', + isActive: true +}) + +// Single user +const user = await userService.getUser('user-id-123') +``` + +### 4. Mutations (Create/Update/Delete) + +```typescript +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { userService } from '@/services' +import { toast } from 'sonner' + +function DeleteUserButton({ userId }) { + const queryClient = useQueryClient() + + const mutation = useMutation({ + mutationFn: () => userService.deleteUser(userId), + onSuccess: () => { + // Refresh the users list + queryClient.invalidateQueries({ queryKey: ['users'] }) + toast.success('User deleted successfully') + }, + onError: (error: any) => { + toast.error(error.response?.data?.message || 'Failed to delete user') + } + }) + + return ( + + ) +} +``` + +--- + +## Common Patterns + +### Pattern 1: Fetching List Data + +```typescript +import { useQuery } from '@tanstack/react-query' +import { userService } from '@/services' +import { useState } from 'react' + +function UsersPage() { + const [page, setPage] = useState(1) + const [search, setSearch] = useState('') + + const { data, isLoading } = useQuery({ + queryKey: ['users', page, search], + queryFn: () => userService.getUsers({ page, limit: 20, search }) + }) + + return ( +
+ setSearch(e.target.value)} + placeholder="Search users..." + /> + + {isLoading ? ( +
Loading...
+ ) : ( +
+ {data?.data.map(user => ( + + ))} + + +
+ )} +
+ ) +} +``` + +### Pattern 2: Creating New Records + +```typescript +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { userService } from '@/services' +import { useState } from 'react' + +function CreateUserForm() { + const queryClient = useQueryClient() + const [formData, setFormData] = useState({ + email: '', + firstName: '', + lastName: '', + role: 'USER' + }) + + const createMutation = useMutation({ + mutationFn: (data) => userService.createUser(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }) + toast.success('User created successfully') + setFormData({ email: '', firstName: '', lastName: '', role: 'USER' }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.message || 'Failed to create user') + } + }) + + const handleSubmit = (e) => { + e.preventDefault() + createMutation.mutate(formData) + } + + return ( +
+ setFormData({ ...formData, email: e.target.value })} + placeholder="Email" + required + /> + {/* More fields... */} + + +
+ ) +} +``` + +### Pattern 3: Updating Records + +```typescript +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { userService } from '@/services' + +function UpdateUserButton({ userId, updates }) { + const queryClient = useQueryClient() + + const updateMutation = useMutation({ + mutationFn: () => userService.updateUser(userId, updates), + onSuccess: () => { + // Refresh both the list and the single user + queryClient.invalidateQueries({ queryKey: ['users'] }) + queryClient.invalidateQueries({ queryKey: ['users', userId] }) + toast.success('User updated successfully') + } + }) + + return ( + + ) +} +``` + +### Pattern 4: File Upload + +```typescript +import { userService } from '@/services' + +function ImportUsersButton() { + const handleFileUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (!file) return + + try { + const result = await userService.importUsers(file) + toast.success(`Imported ${result.imported} users. ${result.failed} failed.`) + } catch (error: any) { + toast.error(error.response?.data?.message || 'Import failed') + } + } + + return ( + + ) +} +``` + +### Pattern 5: File Download + +```typescript +import { userService } from '@/services' + +function ExportUsersButton() { + const handleExport = async () => { + try { + const blob = await userService.exportUsers('csv') + + // Create download link + const url = window.URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `users-${new Date().toISOString()}.csv` + a.click() + + window.URL.revokeObjectURL(url) + toast.success('Users exported successfully') + } catch (error: any) { + toast.error('Export failed') + } + } + + return ( + + ) +} +``` + +--- + +## Service Methods Reference + +### AuthService + +```typescript +import { authService } from '@/services' + +// Login +const response = await authService.login({ + email: 'admin@example.com', + password: 'password' +}) +// Returns: { accessToken, refreshToken, user } + +// Logout +await authService.logout() + +// Refresh token +await authService.refreshToken() + +// Get current user (from localStorage) +const user = authService.getCurrentUser() + +// Check if authenticated +const isAuth = authService.isAuthenticated() + +// Check if admin +const isAdmin = authService.isAdmin() +``` + +### UserService + +```typescript +import { userService } from '@/services' + +// Get paginated users +const users = await userService.getUsers({ + page: 1, + limit: 20, + search: 'john', + role: 'ADMIN', + isActive: true +}) + +// Get single user +const user = await userService.getUser('user-id') + +// Create user +const newUser = await userService.createUser({ + email: 'user@example.com', + firstName: 'John', + lastName: 'Doe', + role: 'USER' +}) + +// Update user +const updated = await userService.updateUser('user-id', { + isActive: false, + role: 'ADMIN' +}) + +// Delete user +await userService.deleteUser('user-id', hard = false) + +// Reset password +const result = await userService.resetPassword('user-id') +// Returns: { temporaryPassword: 'abc123' } + +// Get user activity +const activity = await userService.getUserActivity('user-id', days = 30) + +// Import users from CSV +const result = await userService.importUsers(file) +// Returns: { imported: 10, failed: 2 } + +// Export users to CSV +const blob = await userService.exportUsers('csv') +``` + +### AnalyticsService + +```typescript +import { analyticsService } from '@/services' + +// Get overview statistics +const stats = await analyticsService.getOverview() +// Returns: { totalUsers, activeUsers, totalRevenue, ... } + +// Get user growth data +const growth = await analyticsService.getUserGrowth(days = 30) +// Returns: [{ date: '2024-01-01', users: 100, ... }] + +// Get revenue data +const revenue = await analyticsService.getRevenue('30days') +// Options: '7days', '30days', '90days' + +// Get API usage +const apiUsage = await analyticsService.getApiUsage(days = 7) + +// Get error rate +const errorRate = await analyticsService.getErrorRate(days = 7) + +// Get storage analytics +const storage = await analyticsService.getStorageAnalytics() +``` + +### SecurityService + +```typescript +import { securityService } from '@/services' + +// Get suspicious activity +const suspicious = await securityService.getSuspiciousActivity() +// Returns: { suspiciousIPs: [...], suspiciousEmails: [...] } + +// Get active sessions +const sessions = await securityService.getActiveSessions() + +// Terminate session +await securityService.terminateSession('session-id') + +// Get failed login attempts +const failedLogins = await securityService.getFailedLogins({ + page: 1, + limit: 50, + email: 'user@example.com' +}) + +// Get rate limit violations +const violations = await securityService.getRateLimitViolations(days = 7) + +// Get all API keys +const apiKeys = await securityService.getAllApiKeys() + +// Revoke API key +await securityService.revokeApiKey('key-id') + +// Ban IP address +await securityService.banIpAddress('192.168.1.1', 'Suspicious activity') +``` + +### SystemService + +```typescript +import { systemService } from '@/services' + +// Get system health +const health = await systemService.getHealth() +// Returns: { status: 'healthy', database: 'connected', ... } + +// Get system info +const info = await systemService.getSystemInfo() +// Returns: { version, environment, memory, cpu, ... } + +// Get maintenance status +const maintenance = await systemService.getMaintenanceStatus() + +// Enable maintenance mode +await systemService.enableMaintenance('System upgrade in progress') + +// Disable maintenance mode +await systemService.disableMaintenance() + +// Clear cache +await systemService.clearCache() + +// Run migrations +const result = await systemService.runMigrations() +``` + +### AnnouncementService + +```typescript +import { announcementService } from '@/services' + +// Get all announcements +const announcements = await announcementService.getAnnouncements(activeOnly = false) + +// Get single announcement +const announcement = await announcementService.getAnnouncement('announcement-id') + +// Create announcement +const newAnnouncement = await announcementService.createAnnouncement({ + title: 'Maintenance Notice', + message: 'System will be down for maintenance', + type: 'warning', + priority: 1, + targetAudience: 'all' +}) + +// Update announcement +const updated = await announcementService.updateAnnouncement('announcement-id', { + title: 'Updated Title' +}) + +// Toggle announcement active status +await announcementService.toggleAnnouncement('announcement-id') + +// Delete announcement +await announcementService.deleteAnnouncement('announcement-id') +``` + +### AuditService + +```typescript +import { auditService } from '@/services' + +// Get audit logs +const logs = await auditService.getAuditLogs({ + page: 1, + limit: 50, + userId: 'user-id', + action: 'DELETE', + resourceType: 'user', + startDate: '2024-01-01', + endDate: '2024-12-31' +}) + +// Get audit log by ID +const log = await auditService.getAuditLog('log-id') + +// Get user audit activity +const activity = await auditService.getUserAuditActivity('user-id', days = 30) + +// Get resource history +const history = await auditService.getResourceHistory('user', 'resource-id') + +// Get audit statistics +const stats = await auditService.getAuditStats(startDate, endDate) + +// Export audit logs +const blob = await auditService.exportAuditLogs({ + format: 'csv', + startDate: '2024-01-01', + endDate: '2024-12-31' +}) +``` + +### SettingsService + +```typescript +import { settingsService } from '@/services' + +// Get all settings +const settings = await settingsService.getSettings(category = 'GENERAL') + +// Get single setting +const setting = await settingsService.getSetting('feature_flag') + +// Create setting +const newSetting = await settingsService.createSetting({ + key: 'feature_flag', + value: 'true', + category: 'FEATURES', + description: 'Enable new feature', + isPublic: false +}) + +// Update setting +const updated = await settingsService.updateSetting('feature_flag', { + value: 'false' +}) + +// Delete setting +await settingsService.deleteSetting('feature_flag') + +// Get public settings (for frontend use) +const publicSettings = await settingsService.getPublicSettings() +``` + +--- + +## Error Handling + +### Standard Error Pattern + +All services throw errors with consistent structure: + +```typescript +try { + await userService.deleteUser(userId) + toast.success('User deleted') +} catch (error: any) { + // Error structure: + // error.response.status - HTTP status code + // error.response.data.message - Error message from backend + + const message = error.response?.data?.message || 'Operation failed' + toast.error(message) +} +``` + +### Common HTTP Status Codes + +```typescript +// 400 - Bad Request (validation error) +catch (error: any) { + if (error.response?.status === 400) { + toast.error('Invalid input: ' + error.response.data.message) + } +} + +// 401 - Unauthorized (handled automatically by interceptor) +// Token refresh attempted automatically +// If refresh fails, user redirected to login + +// 403 - Forbidden (no permission) +catch (error: any) { + if (error.response?.status === 403) { + toast.error('You do not have permission to perform this action') + } +} + +// 404 - Not Found +catch (error: any) { + if (error.response?.status === 404) { + toast.error('Resource not found') + } +} + +// 500 - Server Error +catch (error: any) { + if (error.response?.status === 500) { + toast.error('Server error. Please try again later.') + } +} +``` + +### React Query Error Handling + +```typescript +const { data, error } = useQuery({ + queryKey: ['users'], + queryFn: () => userService.getUsers(), + retry: 3, // Retry failed requests + retryDelay: 1000, // Wait 1s between retries +}) + +// Display error +if (error) { + return
Error: {error.message}
+} +``` + +--- + +## Best Practices + +### ✅ DO: Use Services + +```typescript +// Good +import { userService } from '@/services' +const users = await userService.getUsers() +``` + +### ❌ DON'T: Direct API Calls + +```typescript +// Bad - don't do this +import axios from 'axios' +const response = await axios.get('/api/users') +``` + +### ✅ DO: Use React Query + +```typescript +// Good - caching, loading states, error handling +const { data, isLoading, error } = useQuery({ + queryKey: ['users'], + queryFn: () => userService.getUsers() +}) +``` + +### ❌ DON'T: Manual State Management + +```typescript +// Bad - manual state management +const [users, setUsers] = useState([]) +const [loading, setLoading] = useState(false) + +useEffect(() => { + setLoading(true) + userService.getUsers() + .then(setUsers) + .finally(() => setLoading(false)) +}, []) +``` + +### ✅ DO: Specific Query Keys + +```typescript +// Good - specific, cacheable +queryKey: ['users', page, limit, search] +``` + +### ❌ DON'T: Generic Query Keys + +```typescript +// Bad - too generic +queryKey: ['data'] +``` + +### ✅ DO: Invalidate After Mutations + +```typescript +// Good - refresh data after changes +const mutation = useMutation({ + mutationFn: userService.deleteUser, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }) + } +}) +``` + +### ✅ DO: Handle Errors + +```typescript +// Good - user feedback +try { + await userService.deleteUser(id) + toast.success('User deleted') +} catch (error: any) { + toast.error(error.response?.data?.message) +} +``` + +### ❌ DON'T: Ignore Errors + +```typescript +// Bad - no error handling +await userService.deleteUser(id) +``` + +--- + +## Examples + +### Complete CRUD Example + +```typescript +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { userService } from '@/services' +import { toast } from 'sonner' +import { useState } from 'react' + +function UsersManagement() { + const queryClient = useQueryClient() + const [page, setPage] = useState(1) + const [editingUser, setEditingUser] = useState(null) + + // READ - Fetch users + const { data: users, isLoading } = useQuery({ + queryKey: ['users', page], + queryFn: () => userService.getUsers({ page, limit: 20 }) + }) + + // CREATE - Add new user + const createMutation = useMutation({ + mutationFn: (data) => userService.createUser(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }) + toast.success('User created') + } + }) + + // UPDATE - Edit user + const updateMutation = useMutation({ + mutationFn: ({ id, data }) => userService.updateUser(id, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }) + setEditingUser(null) + toast.success('User updated') + } + }) + + // DELETE - Remove user + const deleteMutation = useMutation({ + mutationFn: (id) => userService.deleteUser(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }) + toast.success('User deleted') + } + }) + + if (isLoading) return
Loading...
+ + return ( +
+

Users Management

+ + {/* User List */} + + + + + + + + + + + {users?.data.map(user => ( + + + + + + + ))} + +
EmailNameRoleActions
{user.email}{user.firstName} {user.lastName}{user.role} + + +
+ + {/* Pagination */} +
+ + Page {page} of {users?.totalPages} + +
+
+ ) +} +``` + +--- + +## Summary + +### Key Takeaways + +1. **Always use services** - Never make direct API calls +2. **Use React Query** - For data fetching and caching +3. **Handle errors** - Provide user feedback +4. **Invalidate queries** - After mutations +5. **Use specific query keys** - For better caching + +### Quick Reference + +```typescript +// Import +import { userService } from '@/services' + +// Fetch +const { data } = useQuery({ + queryKey: ['users'], + queryFn: () => userService.getUsers() +}) + +// Mutate +const mutation = useMutation({ + mutationFn: (id) => userService.deleteUser(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }) + toast.success('Success') + } +}) + +// Error handling +try { + await userService.deleteUser(id) +} catch (error: any) { + toast.error(error.response?.data?.message) +} +``` + +--- + +**For more information:** +- [Development Guide](./DEVELOPMENT.md) - General development practices +- [Testing Guide](./TESTING_GUIDE.md) - How to test services +- [Security Guide](./SECURITY.md) - Security best practices diff --git a/dev-docs/API_STANDARDS.md b/dev-docs/API_STANDARDS.md deleted file mode 100644 index 02a42c5..0000000 --- a/dev-docs/API_STANDARDS.md +++ /dev/null @@ -1,476 +0,0 @@ -# API Client Standards - -## Industry Best Practices Implemented - -### 1. Separation of Concerns -- **Public API Instance**: Used for unauthenticated endpoints (login, register, forgot password) -- **Authenticated API Instance**: Used for protected endpoints requiring authentication -- This prevents unnecessary token attachment to public endpoints - -### 2. Cookie-Based Authentication (Recommended) - -#### Configuration -```typescript -withCredentials: true // Enables sending/receiving cookies -``` - -This allows the backend to set httpOnly cookies which are: -- **Secure**: Not accessible via JavaScript (prevents XSS attacks) -- **Automatic**: Browser automatically sends with each request -- **Industry Standard**: Used by major platforms (Google, Facebook, etc.) - -#### Backend Requirements for Cookie-Based Auth - -**Login Response:** -```http -POST /auth/login -Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600 -Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800 - -Response Body: -{ - "user": { - "id": "user-id", - "email": "user@example.com", - "role": "ADMIN" - } -} -``` - -**Cookie Attributes Explained:** -- `HttpOnly`: Prevents JavaScript access (XSS protection) -- `Secure`: Only sent over HTTPS (production) -- `SameSite=Strict`: CSRF protection -- `Path=/`: Cookie scope -- `Max-Age`: Expiration time in seconds - -**Logout:** -```http -POST /auth/logout -Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0 -Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=0 -``` - -**Token Refresh:** -```http -POST /auth/refresh -Cookie: refresh_token= - -Response: -Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600 -``` - -### 3. Fallback: localStorage (Current Implementation) - -For backends that don't support httpOnly cookies, the system falls back to localStorage: -- Token stored in `localStorage.access_token` -- Automatically added to Authorization header -- Less secure than cookies (vulnerable to XSS) - -### 4. Authentication Flow - -#### Login -```typescript -// Uses publicApi (no token required) -adminApiHelpers.login({ email, password }) -``` - -**Response Expected (Cookie-based):** -```json -{ - "user": { - "id": "user-id", - "email": "user@example.com", - "role": "ADMIN", - "firstName": "John", - "lastName": "Doe" - } -} -// + Set-Cookie headers -``` - -**Response Expected (localStorage fallback):** -```json -{ - "access_token": "jwt-token", - "refresh_token": "refresh-token", - "user": { - "id": "user-id", - "email": "user@example.com", - "role": "ADMIN" - } -} -``` - -#### Logout -```typescript -// Centralized logout handling -await adminApiHelpers.logout() -``` -- Calls backend `/auth/logout` to clear httpOnly cookies -- Clears localStorage (access_token, user) -- Prevents duplicate logout logic across components - -#### Token Refresh (Automatic) -```typescript -// Automatically called on 401 response -adminApiHelpers.refreshToken() -``` -- Refreshes expired access token using refresh token -- Retries failed request with new token -- If refresh fails, logs out user - -#### Get Current User -```typescript -adminApiHelpers.getCurrentUser() -``` -- Validates token and fetches current user data -- Useful for session validation - -### 5. Interceptor Improvements - -#### Request Interceptor -- Adds `withCredentials: true` to send cookies -- Adds Authorization header if localStorage token exists (fallback) -- Bearer token format: `Authorization: Bearer ` - -#### Response Interceptor -- **401 Unauthorized**: - - Attempts automatic token refresh - - Retries original request - - If refresh fails, auto-logout and redirect - - Prevents infinite loops on login page -- **403 Forbidden**: Shows permission error toast -- **404 Not Found**: Shows resource not found toast -- **500 Server Error**: Shows server error toast -- **Network Error**: Shows connection error toast - -### 6. Security Best Practices - -✅ **Implemented:** -- Separate public/private API instances -- Cookie support with `withCredentials: true` -- Bearer token authentication (fallback) -- Automatic token injection -- Automatic token refresh on 401 -- Centralized logout with backend call -- Auto-redirect on 401 (with login page check) -- Retry mechanism for failed requests - -✅ **Backend Should Implement:** -- httpOnly cookies for tokens -- Secure flag (HTTPS only) -- SameSite=Strict (CSRF protection) -- Short-lived access tokens (15 min) -- Long-lived refresh tokens (7 days) -- Token rotation on refresh -- Logout endpoint to clear cookies - -⚠️ **Additional Production Recommendations:** -- Rate limiting on login endpoint -- Account lockout after failed attempts -- Two-factor authentication (2FA) -- IP whitelisting for admin access -- Audit logging for all admin actions -- Content Security Policy (CSP) headers -- CORS configuration -- Request/response encryption for sensitive data - -### 7. Security Comparison - -| Feature | localStorage | httpOnly Cookies | -|---------|-------------|------------------| -| XSS Protection | ❌ Vulnerable | ✅ Protected | -| CSRF Protection | ✅ Not vulnerable | ⚠️ Needs SameSite | -| Automatic Sending | ❌ Manual | ✅ Automatic | -| Cross-domain | ✅ Easy | ⚠️ Complex | -| Mobile Apps | ✅ Works | ❌ Limited | -| Industry Standard | ⚠️ Common | ✅ Recommended | - -### 8. Error Handling - -All API errors are consistently handled: -- User-friendly error messages -- Toast notifications for feedback -- Proper error propagation for component-level handling -- Automatic retry on token expiration - -### 9. Type Safety - -All API methods have TypeScript types for: -- Request parameters -- Request body -- Response data (can be improved with response types) - -## Usage Examples - -### Login Flow (Cookie-based) -```typescript -try { - const response = await adminApiHelpers.login({ email, password }) - const { user } = response.data // No access_token in response - - // Verify admin role - if (user.role !== 'ADMIN') { - throw new Error('Admin access required') - } - - // Store user data only (token is in httpOnly cookie) - localStorage.setItem('user', JSON.stringify(user)) - - // Navigate to dashboard - navigate('/admin/dashboard') -} catch (error) { - toast.error('Login failed') -} -``` - -### Authenticated Request -```typescript -// Token automatically sent via cookie or Authorization header -const response = await adminApiHelpers.getUsers({ page: 1, limit: 20 }) -``` - -### Logout -```typescript -// Centralized logout (clears cookies and localStorage) -await adminApiHelpers.logout() -navigate('/login') -``` - -### Automatic Token Refresh -```typescript -// Happens automatically on 401 response -// No manual intervention needed -const response = await adminApiHelpers.getUsers() -// If token expired, it's automatically refreshed and request retried -``` - -## API Endpoint Requirements - -### Authentication Endpoints - -#### POST /auth/login -- **Public endpoint** (no authentication required) -- Validates credentials -- **Cookie-based**: Sets httpOnly cookies in response headers -- **localStorage fallback**: Returns access_token in response body -- Returns user data -- Should verify user role on backend - -**Request:** -```json -{ - "email": "admin@example.com", - "password": "password123" -} -``` - -**Response (Cookie-based):** -```http -HTTP/1.1 200 OK -Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900 -Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800 - -{ - "user": { - "id": "123", - "email": "admin@example.com", - "role": "ADMIN", - "firstName": "John", - "lastName": "Doe" - } -} -``` - -**Response (localStorage fallback):** -```json -{ - "access_token": "eyJhbGc...", - "refresh_token": "eyJhbGc...", - "user": { - "id": "123", - "email": "admin@example.com", - "role": "ADMIN" - } -} -``` - -#### POST /auth/logout -- **Protected endpoint** -- Clears httpOnly cookies -- Invalidates tokens on server -- Clears server-side session - -**Response:** -```http -HTTP/1.1 200 OK -Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0 -Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=0 - -{ - "message": "Logged out successfully" -} -``` - -#### POST /auth/refresh -- **Protected endpoint** -- Reads refresh_token from httpOnly cookie -- Returns new access token -- Implements token rotation (optional) - -**Request:** -```http -Cookie: refresh_token= -``` - -**Response:** -```http -HTTP/1.1 200 OK -Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900 -Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800 - -{ - "message": "Token refreshed" -} -``` - -#### GET /auth/me -- **Protected endpoint** -- Returns current authenticated user -- Useful for session validation - -**Response:** -```json -{ - "id": "123", - "email": "admin@example.com", - "role": "ADMIN", - "firstName": "John", - "lastName": "Doe" -} -``` - -## Backend Implementation Guide - -### Node.js/Express Example - -```javascript -// Login endpoint -app.post('/auth/login', async (req, res) => { - const { email, password } = req.body - - // Validate credentials - const user = await validateUser(email, password) - if (!user) { - return res.status(401).json({ message: 'Invalid credentials' }) - } - - // Generate tokens - const accessToken = generateAccessToken(user) - const refreshToken = generateRefreshToken(user) - - // Set httpOnly cookies - res.cookie('access_token', accessToken, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'strict', - maxAge: 15 * 60 * 1000 // 15 minutes - }) - - res.cookie('refresh_token', refreshToken, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'strict', - path: '/auth/refresh', - maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days - }) - - // Return user data (no tokens in body) - res.json({ user: sanitizeUser(user) }) -}) - -// Logout endpoint -app.post('/auth/logout', (req, res) => { - res.clearCookie('access_token') - res.clearCookie('refresh_token', { path: '/auth/refresh' }) - res.json({ message: 'Logged out successfully' }) -}) - -// Refresh endpoint -app.post('/auth/refresh', async (req, res) => { - const refreshToken = req.cookies.refresh_token - - if (!refreshToken) { - return res.status(401).json({ message: 'No refresh token' }) - } - - try { - const decoded = verifyRefreshToken(refreshToken) - const newAccessToken = generateAccessToken(decoded) - - res.cookie('access_token', newAccessToken, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'strict', - maxAge: 15 * 60 * 1000 - }) - - res.json({ message: 'Token refreshed' }) - } catch (error) { - res.status(401).json({ message: 'Invalid refresh token' }) - } -}) - -// Auth middleware -const authMiddleware = (req, res, next) => { - const token = req.cookies.access_token || - req.headers.authorization?.replace('Bearer ', '') - - if (!token) { - return res.status(401).json({ message: 'No token provided' }) - } - - try { - const decoded = verifyAccessToken(token) - req.user = decoded - next() - } catch (error) { - res.status(401).json({ message: 'Invalid token' }) - } -} -``` - -### CORS Configuration - -```javascript -app.use(cors({ - origin: 'http://localhost:5173', // Your frontend URL - credentials: true // Important: Allow cookies -})) -``` - -## Migration Notes - -If migrating from the old implementation: -1. Login now uses `publicApi` instead of `adminApi` -2. Added `withCredentials: true` for cookie support -3. Logout is centralized and calls backend endpoint -4. Automatic token refresh on 401 responses -5. Backend should set httpOnly cookies instead of returning tokens -6. Frontend stores only user data, not tokens (if using cookies) - -## Testing - -### Test Cookie-based Auth -1. Login and check browser DevTools > Application > Cookies -2. Should see `access_token` and `refresh_token` cookies -3. Cookies should have HttpOnly, Secure, SameSite flags -4. Make authenticated request - cookie sent automatically -5. Logout - cookies should be cleared - -### Test localStorage Fallback -1. Backend returns `access_token` in response body -2. Token stored in localStorage -3. Token added to Authorization header automatically -4. Works for backends without cookie support diff --git a/dev-docs/AUTHENTICATION.md b/dev-docs/AUTHENTICATION.md deleted file mode 100644 index b34eb81..0000000 --- a/dev-docs/AUTHENTICATION.md +++ /dev/null @@ -1,180 +0,0 @@ -# Authentication Setup - -## Overview -The admin dashboard now requires authentication before accessing any admin routes and follows industry-standard security practices. - -## Security Status - -### ✅ Frontend Security (Implemented) -- Protected routes with authentication check -- Role-based access control (ADMIN only) -- httpOnly cookie support (`withCredentials: true`) -- Automatic token refresh on expiration -- Centralized logout with backend call -- localStorage fallback for compatibility -- Secure error handling -- CSRF protection ready (via SameSite cookies) - -### ⚠️ Backend Security (Required) -The backend MUST implement these critical security measures: -1. **httpOnly Cookies**: Store tokens in httpOnly cookies (not response body) -2. **Password Hashing**: Use bcrypt with salt rounds >= 12 -3. **Rate Limiting**: Limit login attempts (5 per 15 minutes) -4. **HTTPS**: Enable HTTPS in production -5. **Token Refresh**: Implement refresh token endpoint -6. **Input Validation**: Sanitize and validate all inputs -7. **CORS**: Configure with specific origin and credentials -8. **Security Headers**: Use helmet.js for security headers -9. **Audit Logging**: Log all admin actions -10. **SQL Injection Prevention**: Use parameterized queries - -See `SECURITY_CHECKLIST.md` for complete requirements. - -## How It Works - -### 1. Protected Routes -All admin routes are wrapped with `ProtectedRoute` component that checks for a valid access token. - -### 2. Login Flow -- User visits any admin route without authentication → Redirected to `/login` -- User enters credentials → API validates and returns user data -- Backend sets httpOnly cookies (recommended) OR returns token (fallback) -- Token/cookies stored, user redirected to originally requested page - -### 3. Token Management -- **Recommended**: Tokens stored in httpOnly cookies (XSS protection) -- **Fallback**: Access token in localStorage, automatically added to requests -- Token automatically sent with all API requests -- On 401 response, automatically attempts token refresh -- If refresh fails, user is logged out and redirected to login - -### 4. Logout -- Calls backend `/auth/logout` to clear httpOnly cookies -- Clears localStorage (access_token, user) -- Redirects to `/login` page - -## API Endpoints Required - -### POST /auth/login -**Request:** -```json -{ - "email": "admin@example.com", - "password": "password123" -} -``` - -**Response (Cookie-based - Recommended):** -```http -HTTP/1.1 200 OK -Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900 -Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800 - -{ - "user": { - "id": "user-id", - "email": "admin@example.com", - "firstName": "Admin", - "lastName": "User", - "role": "ADMIN" - } -} -``` - -**Response (localStorage fallback):** -```json -{ - "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "user": { - "id": "user-id", - "email": "admin@example.com", - "role": "ADMIN" - } -} -``` - -### POST /auth/logout -Clears httpOnly cookies and invalidates tokens. - -### POST /auth/refresh -Refreshes expired access token using refresh token from cookie. - -### GET /auth/me (Optional) -Returns current authenticated user for session validation. - -## Files Modified/Created - -### Created: -- `src/pages/login/index.tsx` - Login page with show/hide password -- `src/components/ProtectedRoute.tsx` - Route protection wrapper -- `dev-docs/AUTHENTICATION.md` - This documentation -- `dev-docs/API_STANDARDS.md` - Detailed API standards -- `dev-docs/SECURITY_CHECKLIST.md` - Complete security checklist - -### Modified: -- `src/App.tsx` - Added login route and protected admin routes -- `src/layouts/app-shell.tsx` - User state management and logout -- `src/lib/api-client.ts` - Cookie support, token refresh, centralized auth - -## Testing - -1. Start the application -2. Navigate to any admin route (e.g., `/admin/dashboard`) -3. Should be redirected to `/login` -4. Enter valid admin credentials -5. Should be redirected back to the dashboard -6. Check browser DevTools > Application > Cookies (if backend uses cookies) -7. Click logout to clear session - -## Security Comparison - -| Feature | Current (Frontend) | With Backend Implementation | -|---------|-------------------|----------------------------| -| XSS Protection | ⚠️ Partial (localStorage) | ✅ Full (httpOnly cookies) | -| CSRF Protection | ✅ Ready | ✅ Full (SameSite cookies) | -| Token Refresh | ✅ Automatic | ✅ Automatic | -| Rate Limiting | ❌ None | ✅ Required | -| Password Hashing | ❌ Backend only | ✅ Required | -| Audit Logging | ❌ Backend only | ✅ Required | -| HTTPS | ⚠️ Production | ✅ Required | - -## Production Deployment Checklist - -### Frontend -- ✅ Build with production environment variables -- ✅ Enable HTTPS -- ✅ Configure CSP headers -- ✅ Set secure cookie flags - -### Backend -- ⚠️ Implement httpOnly cookies -- ⚠️ Enable HTTPS with valid SSL certificate -- ⚠️ Configure CORS with specific origin -- ⚠️ Add rate limiting -- ⚠️ Implement password hashing -- ⚠️ Add security headers (helmet.js) -- ⚠️ Set up audit logging -- ⚠️ Configure environment variables -- ⚠️ Enable database encryption -- ⚠️ Set up monitoring and alerting - -## Security Notes - -### Current Implementation -- Frontend follows industry standards -- Supports both cookie-based and localStorage authentication -- Automatic token refresh prevents session interruption -- Centralized error handling and logout - -### Backend Requirements -- **Critical**: Backend must implement security measures in `SECURITY_CHECKLIST.md` -- **Recommended**: Use httpOnly cookies instead of localStorage -- **Required**: Implement rate limiting, password hashing, HTTPS -- **Important**: Regular security audits and updates - -## Support - -For detailed security requirements, see: -- `dev-docs/SECURITY_CHECKLIST.md` - Complete security checklist -- `dev-docs/API_STANDARDS.md` - API implementation guide -- `dev-docs/DEPLOYMENT.md` - Deployment instructions diff --git a/dev-docs/CI_CD_SETUP.md b/dev-docs/CI_CD_SETUP.md deleted file mode 100644 index 1547342..0000000 --- a/dev-docs/CI_CD_SETUP.md +++ /dev/null @@ -1,209 +0,0 @@ -# CI/CD Setup Guide - -## Overview -This project uses **GitHub Actions** for continuous integration and deployment. - -## Workflows - -### 1. CI Workflow (`.github/workflows/ci.yml`) - -Runs on every push and pull request to main/develop branches. - -**Steps:** -1. Checkout code -2. Setup Node.js (18.x, 20.x matrix) -3. Install dependencies -4. Run linter -5. Run type check -6. Run tests -7. Generate coverage report -8. Upload coverage to Codecov -9. Build application -10. Upload build artifacts -11. Security audit - -### 2. Deploy Workflow (`.github/workflows/deploy.yml`) - -Runs on push to main branch or manual trigger. - -**Steps:** -1. Checkout code -2. Setup Node.js -3. Install dependencies -4. Run tests -5. Build for production -6. Deploy to Netlify/Vercel -7. Notify deployment status - -## Required Secrets - -Configure these in GitHub Settings > Secrets and variables > Actions: - -### For CI -- `CODECOV_TOKEN` - Codecov upload token (optional) -- `SNYK_TOKEN` - Snyk security scanning token (optional) -- `VITE_BACKEND_API_URL` - API URL for build - -### For Deployment - -#### Netlify -- `NETLIFY_AUTH_TOKEN` - Netlify authentication token -- `NETLIFY_SITE_ID` - Netlify site ID -- `VITE_BACKEND_API_URL_PROD` - Production API URL -- `VITE_SENTRY_DSN` - Sentry DSN for error tracking - -#### Vercel (Alternative) -- `VERCEL_TOKEN` - Vercel authentication token -- `VERCEL_ORG_ID` - Vercel organization ID -- `VERCEL_PROJECT_ID` - Vercel project ID -- `VITE_BACKEND_API_URL_PROD` - Production API URL -- `VITE_SENTRY_DSN` - Sentry DSN - -## Setup Instructions - -### 1. Enable GitHub Actions -GitHub Actions is enabled by default for all repositories. - -### 2. Configure Secrets - -Go to your repository: -``` -Settings > Secrets and variables > Actions > New repository secret -``` - -Add all required secrets listed above. - -### 3. Configure Codecov (Optional) - -1. Sign up at [codecov.io](https://codecov.io) -2. Add your repository -3. Copy the upload token -4. Add as `CODECOV_TOKEN` secret - -### 4. Configure Netlify - -1. Sign up at [netlify.com](https://netlify.com) -2. Create a new site -3. Get your Site ID from Site settings -4. Generate a Personal Access Token -5. Add both as secrets - -### 5. Configure Vercel (Alternative) - -1. Sign up at [vercel.com](https://vercel.com) -2. Install Vercel CLI: `npm i -g vercel` -3. Run `vercel login` -4. Run `vercel link` in your project -5. Get tokens from Vercel dashboard -6. Add as secrets - -## Manual Deployment - -### Trigger via GitHub UI -1. Go to Actions tab -2. Select "Deploy to Production" -3. Click "Run workflow" -4. Select branch -5. Click "Run workflow" - -### Trigger via CLI -```bash -gh workflow run deploy.yml -``` - -## Monitoring - -### View Workflow Runs -``` -Repository > Actions tab -``` - -### View Logs -Click on any workflow run to see detailed logs. - -### Notifications -Configure notifications in: -``` -Settings > Notifications > Actions -``` - -## Troubleshooting - -### Build Fails -1. Check logs in Actions tab -2. Verify all secrets are set correctly -3. Test build locally: `npm run build` - -### Tests Fail -1. Run tests locally: `npm run test:run` -2. Check for environment-specific issues -3. Verify test setup is correct - -### Deployment Fails -1. Check deployment logs -2. Verify API URL is correct -3. Check Netlify/Vercel dashboard for errors - -## Best Practices - -1. **Always run tests before merging** -2. **Use pull requests for code review** -3. **Keep secrets secure** - never commit them -4. **Monitor build times** - optimize if needed -5. **Review security audit results** -6. **Keep dependencies updated** - -## Advanced Configuration - -### Branch Protection Rules - -Recommended settings: -``` -Settings > Branches > Add rule - -Branch name pattern: main -☑ Require a pull request before merging -☑ Require status checks to pass before merging - - test - - build -☑ Require branches to be up to date before merging -☑ Do not allow bypassing the above settings -``` - -### Caching - -The workflows use npm caching to speed up builds: -```yaml -- uses: actions/setup-node@v4 - with: - cache: 'npm' -``` - -### Matrix Testing - -Tests run on multiple Node.js versions: -```yaml -strategy: - matrix: - node-version: [18.x, 20.x] -``` - -## Cost Optimization - -GitHub Actions is free for public repositories and includes: -- 2,000 minutes/month for private repos (free tier) -- Unlimited for public repos - -Tips to reduce usage: -1. Use caching -2. Run tests only on changed files -3. Skip redundant jobs -4. Use self-hosted runners for heavy workloads - -## Resources - -- [GitHub Actions Documentation](https://docs.github.com/en/actions) -- [Netlify Deploy Action](https://github.com/nwtgck/actions-netlify) -- [Vercel Deploy Action](https://github.com/amondnet/vercel-action) -- [Codecov Action](https://github.com/codecov/codecov-action) - diff --git a/dev-docs/DEPLOYMENT_OPTIONS.md b/dev-docs/DEPLOYMENT_OPTIONS.md deleted file mode 100644 index 2c710ef..0000000 --- a/dev-docs/DEPLOYMENT_OPTIONS.md +++ /dev/null @@ -1,393 +0,0 @@ -# Deployment Options - Industry Standard - -## ✅ Your Project Has All Major Deployment Configurations! - -Your project includes deployment configs for: -1. **Vercel** (vercel.json) -2. **Netlify** (netlify.toml) -3. **Docker** (Dockerfile + nginx.conf) -4. **GitHub Actions** (CI/CD workflows) - -This makes your project **deployment-ready** for any platform! - ---- - -## 1. Vercel Deployment ⚡ - -**File:** `vercel.json` - -### Features: -- ✅ Production build command -- ✅ SPA routing (rewrites) -- ✅ Security headers -- ✅ Asset caching (1 year) -- ✅ XSS protection -- ✅ Clickjacking protection - -### Deploy: -```bash -# Install Vercel CLI -npm i -g vercel - -# Deploy -vercel - -# Deploy to production -vercel --prod -``` - -### Or via GitHub: -1. Connect repository to Vercel -2. Auto-deploys on push to main -3. Preview deployments for PRs - -### Environment Variables: -Set in Vercel dashboard: -- `VITE_API_URL` - Your production API URL -- `VITE_SENTRY_DSN` - Sentry error tracking - ---- - -## 2. Netlify Deployment 🌐 - -**File:** `netlify.toml` - -### Features: -- ✅ Production build command -- ✅ SPA routing (redirects) -- ✅ Security headers -- ✅ Asset caching -- ✅ Node.js 18 environment - -### Deploy: -```bash -# Install Netlify CLI -npm i -g netlify-cli - -# Deploy -netlify deploy - -# Deploy to production -netlify deploy --prod -``` - -### Or via GitHub: -1. Connect repository to Netlify -2. Auto-deploys on push to main -3. Deploy previews for PRs - -### Environment Variables: -Set in Netlify dashboard: -- `VITE_API_URL` -- `VITE_SENTRY_DSN` - ---- - -## 3. Docker Deployment 🐳 - -**Files:** `Dockerfile` + `nginx.conf` - -### Features: -- ✅ Multi-stage build (optimized) -- ✅ Nginx web server -- ✅ Gzip compression -- ✅ Security headers -- ✅ Health checks -- ✅ Asset caching -- ✅ Production-ready - -### Build & Run: -```bash -# Build image -docker build -t yaltopia-admin . - -# Run container -docker run -p 80:80 yaltopia-admin - -# Or with environment variables -docker run -p 80:80 \ - -e VITE_API_URL=https://api.yourdomain.com/api/v1 \ - yaltopia-admin -``` - -### Deploy to Cloud: -- **AWS ECS/Fargate** -- **Google Cloud Run** -- **Azure Container Instances** -- **DigitalOcean App Platform** -- **Kubernetes** - ---- - -## 4. GitHub Actions CI/CD 🚀 - -**Files:** `.github/workflows/ci.yml` + `.github/workflows/deploy.yml` - -### Features: -- ✅ Automated testing -- ✅ Linting & type checking -- ✅ Security scanning -- ✅ Code coverage -- ✅ Automated deployment -- ✅ Multi-node testing (18.x, 20.x) - -### Triggers: -- Push to main/develop -- Pull requests -- Manual workflow dispatch - ---- - -## Security Headers Comparison - -All deployment configs include these security headers: - -| Header | Purpose | Included | -|--------|---------|----------| -| X-Frame-Options | Prevent clickjacking | ✅ | -| X-Content-Type-Options | Prevent MIME sniffing | ✅ | -| X-XSS-Protection | XSS protection | ✅ | -| Referrer-Policy | Control referrer info | ✅ | -| Cache-Control | Asset caching | ✅ | - ---- - -## Performance Optimizations - -### All Configs Include: -1. **Gzip Compression** - Reduce file sizes -2. **Asset Caching** - 1 year cache for static files -3. **Production Build** - Minified, optimized code -4. **Code Splitting** - Vendor chunks separated -5. **Tree Shaking** - Remove unused code - ---- - -## Comparison: Which to Use? - -### Vercel ⚡ -**Best for:** -- Fastest deployment -- Automatic HTTPS -- Edge network (CDN) -- Serverless functions -- Preview deployments - -**Pros:** -- Zero config needed -- Excellent DX -- Fast global CDN -- Free tier generous - -**Cons:** -- Vendor lock-in -- Limited customization - ---- - -### Netlify 🌐 -**Best for:** -- Static sites -- Form handling -- Split testing -- Identity/Auth -- Functions - -**Pros:** -- Easy to use -- Great free tier -- Built-in forms -- Deploy previews - -**Cons:** -- Slower than Vercel -- Limited compute - ---- - -### Docker 🐳 -**Best for:** -- Full control -- Any cloud provider -- Kubernetes -- On-premise -- Complex setups - -**Pros:** -- Complete control -- Portable -- Scalable -- No vendor lock-in - -**Cons:** -- More complex -- Need to manage infra -- Requires DevOps knowledge - ---- - -## Industry Standards Checklist - -Your project has: - -### Deployment ✅ -- [x] Multiple deployment options -- [x] Vercel configuration -- [x] Netlify configuration -- [x] Docker support -- [x] CI/CD pipelines - -### Security ✅ -- [x] Security headers -- [x] XSS protection -- [x] Clickjacking protection -- [x] MIME sniffing prevention -- [x] Referrer policy - -### Performance ✅ -- [x] Gzip compression -- [x] Asset caching -- [x] Code splitting -- [x] Production builds -- [x] Optimized images - -### DevOps ✅ -- [x] Automated testing -- [x] Automated deployment -- [x] Environment variables -- [x] Health checks (Docker) -- [x] Multi-stage builds - -### Documentation ✅ -- [x] Deployment guides -- [x] Environment setup -- [x] API documentation -- [x] Security checklist -- [x] Troubleshooting - ---- - -## Quick Start Deployment - -### Option 1: Vercel (Fastest) -```bash -npm i -g vercel -vercel login -vercel -``` - -### Option 2: Netlify -```bash -npm i -g netlify-cli -netlify login -netlify deploy --prod -``` - -### Option 3: Docker -```bash -docker build -t yaltopia-admin . -docker run -p 80:80 yaltopia-admin -``` - ---- - -## Environment Variables - -All platforms need these: - -```env -# Required -VITE_API_URL=https://api.yourdomain.com/api/v1 - -# Optional -VITE_SENTRY_DSN=https://your-sentry-dsn -VITE_ENV=production -``` - ---- - -## Cost Comparison - -### Vercel -- **Free:** Hobby projects -- **Pro:** $20/month -- **Enterprise:** Custom - -### Netlify -- **Free:** Personal projects -- **Pro:** $19/month -- **Business:** $99/month - -### Docker (AWS) -- **ECS Fargate:** ~$15-50/month -- **EC2:** ~$10-100/month -- **Depends on:** Traffic, resources - ---- - -## Recommendation - -### For This Project: -1. **Development:** Local + GitHub Actions -2. **Staging:** Vercel/Netlify (free tier) -3. **Production:** - - Small scale: Vercel/Netlify - - Large scale: Docker + AWS/GCP - - Enterprise: Kubernetes - ---- - -## What Makes This Industry Standard? - -✅ **Multiple Deployment Options** -- Not locked to one platform -- Can deploy anywhere - -✅ **Security First** -- All security headers configured -- XSS, clickjacking protection -- HTTPS ready - -✅ **Performance Optimized** -- Caching strategies -- Compression enabled -- CDN ready - -✅ **CI/CD Ready** -- Automated testing -- Automated deployment -- Quality gates - -✅ **Production Ready** -- Health checks -- Error monitoring -- Logging ready - -✅ **Well Documented** -- Clear instructions -- Multiple options -- Troubleshooting guides - ---- - -## Next Steps - -1. **Choose Platform:** Vercel, Netlify, or Docker -2. **Set Environment Variables** -3. **Deploy:** Follow quick start above -4. **Configure Domain:** Point to deployment -5. **Enable Monitoring:** Sentry, analytics -6. **Set Up Alerts:** Error notifications - ---- - -## Support - -- [Vercel Docs](https://vercel.com/docs) -- [Netlify Docs](https://docs.netlify.com) -- [Docker Docs](https://docs.docker.com) -- [GitHub Actions Docs](https://docs.github.com/en/actions) - ---- - -**Your project is deployment-ready for any platform!** 🚀 diff --git a/dev-docs/DEVELOPMENT.md b/dev-docs/DEVELOPMENT.md new file mode 100644 index 0000000..967f437 --- /dev/null +++ b/dev-docs/DEVELOPMENT.md @@ -0,0 +1,294 @@ +# Development Guide + +Complete guide for developing the Yaltopia Ticket Admin application. + +## Tech Stack + +- **Frontend**: React 18 + TypeScript + Vite +- **UI**: TailwindCSS + shadcn/ui +- **State**: React Query (TanStack Query) +- **Routing**: React Router v6 +- **HTTP Client**: Axios +- **Forms**: React Hook Form + Zod +- **Charts**: Recharts +- **Notifications**: Sonner +- **Error Tracking**: Sentry +- **Testing**: Vitest + Testing Library + +## Quick Start + +```bash +# Install dependencies +npm install + +# Set up environment +cp .env.example .env +# Edit .env with your backend URL + +# Start development server +npm run dev + +# Run tests +npm run test + +# Build for production +npm run build +``` + +## Project Structure + +``` +src/ +├── components/ # Reusable UI components +├── pages/ # Page components +├── services/ # API service layer +│ ├── api/ +│ │ └── client.ts # Axios instance +│ ├── auth.service.ts +│ ├── user.service.ts +│ └── ... +├── layouts/ # Layout components +├── lib/ # Utilities +└── test/ # Test utilities +``` + +## API Architecture + +### Service Layer Pattern + +All API calls go through typed service classes: + +``` +Component → Service → API Client → Backend +``` + +### Available Services + +```typescript +import { + authService, // Authentication + userService, // User management + analyticsService, // Analytics + securityService, // Security + systemService, // System health + announcementService,// Announcements + auditService, // Audit logs + settingsService // Settings +} from '@/services' +``` + +### Usage Examples + +**Fetching Data:** +```typescript +import { useQuery } from '@tanstack/react-query' +import { userService } from '@/services' + +const { data, isLoading } = useQuery({ + queryKey: ['users'], + queryFn: () => userService.getUsers({ page: 1, limit: 20 }) +}) +``` + +**Mutations:** +```typescript +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { userService } from '@/services' + +const queryClient = useQueryClient() +const mutation = useMutation({ + mutationFn: (id: string) => userService.deleteUser(id), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }) + toast.success('User deleted') + } +}) +``` + +**Direct Calls:** +```typescript +import { authService } from '@/services' + +const response = await authService.login({ email, password }) +``` + +## Authentication + +### Setup + +1. Backend must return tokens on login +2. Frontend stores in httpOnly cookies (recommended) or localStorage +3. All requests automatically include auth token +4. 401 errors trigger automatic token refresh + +### Login Flow + +```typescript +// User logs in +const response = await authService.login({ email, password }) + +// Token stored automatically +// User redirected to dashboard + +// All subsequent requests include token +await userService.getUsers() // Token added automatically +``` + +### Protected Routes + +```typescript +}> + } /> + +``` + +### Logout + +```typescript +await authService.logout() // Clears tokens & cookies +navigate('/login') +``` + +## API Standards + +### Service Methods + +All service methods: +- Return typed data (no `response.data` unwrapping needed) +- Throw errors with `error.response.data.message` +- Use consistent naming (get, create, update, delete) + +### Error Handling + +```typescript +try { + await userService.deleteUser(id) + toast.success('User deleted') +} catch (error: any) { + toast.error(error.response?.data?.message || 'Operation failed') +} +``` + +### Type Safety + +```typescript +// All responses are typed +const users: PaginatedResponse = await userService.getUsers() +const stats: OverviewStats = await analyticsService.getOverview() +``` + +## Environment Variables + +```bash +# Required +VITE_BACKEND_API_URL=http://localhost:3001/api/v1 + +# Optional (Sentry) +VITE_SENTRY_DSN=your-sentry-dsn +VITE_SENTRY_ENVIRONMENT=development +``` + +## Common Tasks + +### Adding a New Service Method + +```typescript +// src/services/user.service.ts +async exportUserData(userId: string): Promise { + const response = await apiClient.get(`/admin/users/${userId}/export`, { + responseType: 'blob' + }) + return response.data +} +``` + +### Adding a New Page + +1. Create page component in `src/pages/` +2. Add route in `src/App.tsx` +3. Import required services +4. Use React Query for data fetching + +### Adding a New Component + +1. Create in `src/components/` +2. Use TypeScript for props +3. Follow existing patterns +4. Add to component exports if reusable + +## Best Practices + +### React Query + +```typescript +// Good - specific query keys +queryKey: ['users', page, limit, search] + +// Bad - too generic +queryKey: ['data'] +``` + +### Service Layer + +```typescript +// Good - use services +import { userService } from '@/services' +await userService.getUsers() + +// Bad - direct axios +import axios from 'axios' +await axios.get('/api/users') +``` + +### Error Handling + +```typescript +// Good - handle errors +try { + await userService.deleteUser(id) +} catch (error: any) { + toast.error(error.response?.data?.message) +} + +// Bad - no error handling +await userService.deleteUser(id) +``` + +### Type Safety + +```typescript +// Good - use types +const users: PaginatedResponse = await userService.getUsers() + +// Bad - any type +const users: any = await userService.getUsers() +``` + +## Troubleshooting + +### CORS Errors +- Ensure backend has CORS configured +- Check `withCredentials: true` in API client +- Verify `VITE_BACKEND_API_URL` is correct + +### 401 Errors +- Check token is being sent +- Verify backend token validation +- Check token expiration + +### Build Errors +- Run `npm run build` to check TypeScript errors +- Fix any type errors +- Ensure all imports are correct + +### Test Failures +- Run `npm run test` to see failures +- Check mock implementations +- Verify test data matches types + +## Additional Resources + +- [Testing Guide](./TESTING.md) +- [Deployment Guide](./DEPLOYMENT.md) +- [Security Guide](./SECURITY.md) +- [Troubleshooting](./TROUBLESHOOTING.md) diff --git a/dev-docs/ERROR_MONITORING.md b/dev-docs/ERROR_MONITORING.md deleted file mode 100644 index bfa762b..0000000 --- a/dev-docs/ERROR_MONITORING.md +++ /dev/null @@ -1,231 +0,0 @@ -# Error Monitoring with Sentry - -## Overview -This project uses **Sentry** for error tracking and performance monitoring. - -## Setup - -### 1. Create Sentry Account -1. Sign up at [sentry.io](https://sentry.io) -2. Create a new project -3. Select "React" as the platform -4. Copy your DSN - -### 2. Configure Environment Variables - -Add to `.env.production`: -```env -VITE_SENTRY_DSN=https://your-key@sentry.io/your-project-id -``` - -### 3. Sentry is Already Integrated - -The following files have Sentry integration: -- `src/lib/sentry.ts` - Sentry initialization -- `src/main.tsx` - Sentry init on app start -- `src/components/ErrorBoundary.tsx` - Error boundary with Sentry - -## Features - -### 1. Error Tracking -All uncaught errors are automatically sent to Sentry: -```typescript -try { - // Your code -} catch (error) { - Sentry.captureException(error) -} -``` - -### 2. Performance Monitoring -Tracks page load times and API calls: -```typescript -tracesSampleRate: 0.1 // 10% of transactions -``` - -### 3. Session Replay -Records user sessions when errors occur: -```typescript -replaysOnErrorSampleRate: 1.0 // 100% on errors -replaysSessionSampleRate: 0.1 // 10% of normal sessions -``` - -### 4. Error Filtering -Filters out browser extension errors: -```typescript -beforeSend(event, hint) { - // Filter logic -} -``` - -## Manual Error Logging - -### Capture Exception -```typescript -import { Sentry } from '@/lib/sentry' - -try { - // risky operation -} catch (error) { - Sentry.captureException(error, { - tags: { - section: 'user-management', - }, - extra: { - userId: user.id, - }, - }) -} -``` - -### Capture Message -```typescript -Sentry.captureMessage('Something important happened', 'info') -``` - -### Add Breadcrumbs -```typescript -Sentry.addBreadcrumb({ - category: 'auth', - message: 'User logged in', - level: 'info', -}) -``` - -### Set User Context -```typescript -Sentry.setUser({ - id: user.id, - email: user.email, - username: user.name, -}) -``` - -## Dashboard Features - -### 1. Issues -View all errors with: -- Stack traces -- User context -- Breadcrumbs -- Session replays - -### 2. Performance -Monitor: -- Page load times -- API response times -- Slow transactions - -### 3. Releases -Track errors by release version: -```bash -# Set release in build -VITE_SENTRY_RELEASE=1.0.0 npm run build -``` - -### 4. Alerts -Configure alerts for: -- New issues -- Spike in errors -- Performance degradation - -## Best Practices - -### 1. Environment Configuration -```typescript -// Only enable in production -if (environment !== 'development') { - Sentry.init({ ... }) -} -``` - -### 2. Sample Rates -```typescript -// Production -tracesSampleRate: 0.1 // 10% -replaysSessionSampleRate: 0.1 // 10% - -// Staging -tracesSampleRate: 1.0 // 100% -replaysSessionSampleRate: 0.5 // 50% -``` - -### 3. PII Protection -```typescript -replaysIntegration({ - maskAllText: true, - blockAllMedia: true, -}) -``` - -### 4. Error Grouping -Use fingerprinting for better grouping: -```typescript -beforeSend(event) { - event.fingerprint = ['{{ default }}', event.message] - return event -} -``` - -## Troubleshooting - -### Errors Not Appearing -1. Check DSN is correct -2. Verify environment is not 'development' -3. Check browser console for Sentry errors -4. Verify network requests to Sentry - -### Too Many Events -1. Reduce sample rates -2. Add more filters in beforeSend -3. Set up rate limiting in Sentry dashboard - -### Missing Context -1. Add more breadcrumbs -2. Set user context after login -3. Add custom tags and extra data - -## Cost Management - -Sentry pricing is based on: -- Number of events -- Number of replays -- Data retention - -Tips to reduce costs: -1. Lower sample rates in production -2. Filter out noisy errors -3. Use error grouping effectively -4. Set up spike protection - -## Integration with CI/CD - -### Upload Source Maps -```yaml -# In .github/workflows/deploy.yml -- name: Upload source maps to Sentry - run: | - npm install -g @sentry/cli - sentry-cli releases new ${{ github.sha }} - sentry-cli releases files ${{ github.sha }} upload-sourcemaps ./dist - sentry-cli releases finalize ${{ github.sha }} - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_ORG: your-org - SENTRY_PROJECT: your-project -``` - -## Resources - -- [Sentry React Documentation](https://docs.sentry.io/platforms/javascript/guides/react/) -- [Sentry Performance Monitoring](https://docs.sentry.io/product/performance/) -- [Sentry Session Replay](https://docs.sentry.io/product/session-replay/) -- [Sentry Best Practices](https://docs.sentry.io/product/best-practices/) - -## Support - -For issues with Sentry integration: -1. Check Sentry documentation -2. Review browser console -3. Check Sentry dashboard -4. Contact Sentry support diff --git a/dev-docs/ERROR_TRACKING_FALLBACK.md b/dev-docs/ERROR_TRACKING_FALLBACK.md deleted file mode 100644 index cffb0e0..0000000 --- a/dev-docs/ERROR_TRACKING_FALLBACK.md +++ /dev/null @@ -1,392 +0,0 @@ -# Error Tracking with Fallback System - -## Overview - -This project uses a **dual error tracking system**: -1. **Primary**: Sentry (cloud-based) -2. **Fallback**: Custom backend logging (if Sentry fails) - -## Architecture - -``` -Error Occurs - ↓ -Try Sentry First - ↓ -If Sentry Fails → Queue for Backend - ↓ -Send to Backend API: POST /api/v1/errors/log -``` - -## Usage - -### Basic Error Tracking - -```typescript -import { errorTracker } from '@/lib/error-tracker' - -try { - await riskyOperation() -} catch (error) { - errorTracker.trackError(error, { - tags: { section: 'payment' }, - extra: { orderId: '123' }, - userId: user.id - }) -} -``` - -### Track Messages - -```typescript -errorTracker.trackMessage('Payment processed successfully', 'info', { - amount: 100, - currency: 'USD' -}) -``` - -### Set User Context - -```typescript -// After login -errorTracker.setUser({ - id: user.id, - email: user.email, - name: user.name -}) - -// On logout -errorTracker.clearUser() -``` - -### Add Breadcrumbs - -```typescript -errorTracker.addBreadcrumb('navigation', 'User clicked checkout button', 'info') -``` - -## Backend API Required - -Your backend needs to implement this endpoint: - -### POST /api/v1/errors/log - -**Request Body:** -```json -{ - "message": "Error message", - "stack": "Error stack trace", - "url": "https://app.example.com/dashboard", - "userAgent": "Mozilla/5.0...", - "timestamp": "2024-02-24T10:30:00.000Z", - "userId": "user-123", - "extra": { - "section": "payment", - "orderId": "123" - } -} -``` - -**Response:** -```json -{ - "success": true, - "logId": "log-456" -} -``` - -### Backend Implementation Example (Node.js/Express) - -```javascript -// routes/errors.js -router.post('/errors/log', async (req, res) => { - try { - const { message, stack, url, userAgent, timestamp, userId, extra } = req.body - - // Save to database - await ErrorLog.create({ - message, - stack, - url, - userAgent, - timestamp: new Date(timestamp), - userId, - extra: JSON.stringify(extra) - }) - - // Optional: Send alert for critical errors - if (message.includes('payment') || message.includes('auth')) { - await sendSlackAlert(message, stack) - } - - res.json({ success: true }) - } catch (error) { - console.error('Failed to log error:', error) - res.status(500).json({ success: false }) - } -}) -``` - -### Database Schema - -```sql -CREATE TABLE error_logs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - message TEXT NOT NULL, - stack TEXT, - url TEXT NOT NULL, - user_agent TEXT, - timestamp TIMESTAMP NOT NULL, - user_id UUID, - extra JSONB, - created_at TIMESTAMP DEFAULT NOW() -); - -CREATE INDEX idx_error_logs_timestamp ON error_logs(timestamp DESC); -CREATE INDEX idx_error_logs_user_id ON error_logs(user_id); -``` - -## How It Works - -### 1. Automatic Global Error Handling - -All uncaught errors are automatically tracked: - -```typescript -// Automatically catches all errors -window.addEventListener('error', (event) => { - errorTracker.trackError(new Error(event.message)) -}) - -// Catches unhandled promise rejections -window.addEventListener('unhandledrejection', (event) => { - errorTracker.trackError(event.reason) -}) -``` - -### 2. Queue System - -Errors are queued and sent to backend: -- Max queue size: 50 errors -- Automatic retry on failure -- Prevents memory leaks - -### 3. Dual Tracking - -Every error is sent to: -1. **Sentry** (if available) - Rich debugging features -2. **Backend** (always) - Your own database for compliance/analysis - -## Sentry Alternatives - -If you want to replace Sentry entirely: - -### 1. LogRocket -```bash -npm install logrocket -``` - -```typescript -import LogRocket from 'logrocket' - -LogRocket.init('your-app-id') - -// Track errors -LogRocket.captureException(error) -``` - -### 2. Rollbar -```bash -npm install rollbar -``` - -```typescript -import Rollbar from 'rollbar' - -const rollbar = new Rollbar({ - accessToken: 'your-token', - environment: 'production' -}) - -rollbar.error(error) -``` - -### 3. Bugsnag -```bash -npm install @bugsnag/js @bugsnag/plugin-react -``` - -```typescript -import Bugsnag from '@bugsnag/js' -import BugsnagPluginReact from '@bugsnag/plugin-react' - -Bugsnag.start({ - apiKey: 'your-api-key', - plugins: [new BugsnagPluginReact()] -}) -``` - -### 4. Self-Hosted GlitchTip -```bash -# Docker Compose -docker-compose up -d -``` - -Free, open-source, Sentry-compatible API. - -## Benefits of Fallback System - -### 1. Reliability -- Never lose error data if Sentry is down -- Backend always receives errors - -### 2. Compliance -- Keep error logs in your own database -- Meet data residency requirements -- Full control over sensitive data - -### 3. Cost Control -- Reduce Sentry event count -- Use backend for high-volume errors -- Keep Sentry for detailed debugging - -### 4. Custom Analysis -- Query errors with SQL -- Build custom dashboards -- Integrate with your alerting system - -## Configuration - -### Enable/Disable Fallback - -```typescript -// src/lib/error-tracker.ts - -// Disable backend logging (Sentry only) -const ENABLE_BACKEND_LOGGING = false - -private queueError(errorLog: ErrorLog) { - if (!ENABLE_BACKEND_LOGGING) return - // ... rest of code -} -``` - -### Adjust Queue Size - -```typescript -private maxQueueSize = 100 // Increase for high-traffic apps -``` - -### Change Backend Endpoint - -```typescript -await apiClient.post('/custom/error-endpoint', errorLog) -``` - -## Monitoring Dashboard - -Build a simple error dashboard: - -```typescript -// Backend endpoint -router.get('/errors/stats', async (req, res) => { - const stats = await db.query(` - SELECT - DATE(timestamp) as date, - COUNT(*) as count, - COUNT(DISTINCT user_id) as affected_users - FROM error_logs - WHERE timestamp > NOW() - INTERVAL '7 days' - GROUP BY DATE(timestamp) - ORDER BY date DESC - `) - - res.json(stats) -}) -``` - -## Best Practices - -### 1. Don't Log Everything -```typescript -// Bad: Logging expected errors -if (!user) { - errorTracker.trackError(new Error('User not found')) -} - -// Good: Only log unexpected errors -try { - await criticalOperation() -} catch (error) { - errorTracker.trackError(error) -} -``` - -### 2. Add Context -```typescript -errorTracker.trackError(error, { - extra: { - action: 'checkout', - step: 'payment', - amount: 100 - } -}) -``` - -### 3. Set User Context Early -```typescript -// In your auth flow -useEffect(() => { - if (user) { - errorTracker.setUser({ - id: user.id, - email: user.email, - name: user.name - }) - } -}, [user]) -``` - -### 4. Clean Up on Logout -```typescript -const handleLogout = () => { - errorTracker.clearUser() - // ... rest of logout -} -``` - -## Troubleshooting - -### Errors Not Reaching Backend - -1. Check network tab for failed requests -2. Verify backend endpoint exists -3. Check CORS configuration -4. Review backend logs - -### Queue Growing Too Large - -1. Increase `maxQueueSize` -2. Check backend availability -3. Add retry logic with exponential backoff - -### Duplicate Errors - -This is intentional - errors go to both Sentry and backend. To disable: - -```typescript -// Only send to backend if Sentry fails -try { - Sentry.captureException(error) -} catch (sentryError) { - this.queueError(errorLog) // Only fallback -} -``` - -## Resources - -- [Sentry Documentation](https://docs.sentry.io/) -- [LogRocket Documentation](https://docs.logrocket.com/) -- [Rollbar Documentation](https://docs.rollbar.com/) -- [GlitchTip (Self-hosted)](https://glitchtip.com/) - diff --git a/dev-docs/LOGIN_API_DOCUMENTATION.md b/dev-docs/LOGIN_API_DOCUMENTATION.md deleted file mode 100644 index 4065596..0000000 --- a/dev-docs/LOGIN_API_DOCUMENTATION.md +++ /dev/null @@ -1,357 +0,0 @@ -# Login API Documentation - -## Endpoint -``` -POST /api/v1/auth/login -``` - -## Description -Login user with email or phone number. This endpoint authenticates users using either email address or phone number along with password. - -## Current Implementation - -### Frontend Code - -**API Client** (`src/lib/api-client.ts`): -```typescript -export const adminApiHelpers = { - // Auth - uses publicApi (no token required) - login: (data: { email: string; password: string }) => - publicApi.post('/auth/login', data), - // ... -} -``` - -**Login Page** (`src/pages/login/index.tsx`): -```typescript -const handleLogin = async (e: React.FormEvent) => { - e.preventDefault() - setIsLoading(true) - - try { - const response = await adminApiHelpers.login({ email, password }) - const { access_token, user } = response.data - - // Check if user is admin - if (user.role !== 'ADMIN') { - toast.error("Access denied. Admin privileges required.") - return - } - - // Store credentials - if (access_token) { - localStorage.setItem('access_token', access_token) - } - localStorage.setItem('user', JSON.stringify(user)) - - toast.success("Login successful!") - navigate(from, { replace: true }) - } catch (error: any) { - const message = error.response?.data?.message || "Invalid email or password" - toast.error(message) - } finally { - setIsLoading(false) - } -} -``` - -## Request - -### Headers -``` -Content-Type: application/json -``` - -### Body (JSON) - -**Option 1: Email + Password** -```json -{ - "email": "admin@example.com", - "password": "your-password" -} -``` - -**Option 2: Phone + Password** (if backend supports) -```json -{ - "phone": "+1234567890", - "password": "your-password" -} -``` - -### Example Request -```bash -curl -X POST https://api.yourdomain.com/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{ - "email": "admin@example.com", - "password": "password123" - }' -``` - -## Response - -### Success Response (200 OK) - -**Option 1: With Access Token in Body** (localStorage fallback) -```json -{ - "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "user": { - "id": "user-id-123", - "email": "admin@example.com", - "firstName": "John", - "lastName": "Doe", - "role": "ADMIN", - "isActive": true, - "createdAt": "2024-01-01T00:00:00.000Z" - } -} -``` - -**Option 2: With httpOnly Cookies** (recommended) -```http -HTTP/1.1 200 OK -Set-Cookie: access_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900 -Set-Cookie: refresh_token=; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh; Max-Age=604800 -Content-Type: application/json - -{ - "user": { - "id": "user-id-123", - "email": "admin@example.com", - "firstName": "John", - "lastName": "Doe", - "role": "ADMIN", - "isActive": true, - "createdAt": "2024-01-01T00:00:00.000Z" - } -} -``` - -### Error Responses - -**401 Unauthorized** - Invalid credentials -```json -{ - "message": "Invalid email or password", - "statusCode": 401 -} -``` - -**403 Forbidden** - Account inactive or not admin -```json -{ - "message": "Account is inactive", - "statusCode": 403 -} -``` - -**400 Bad Request** - Validation error -```json -{ - "message": "Validation failed", - "errors": [ - { - "field": "email", - "message": "Invalid email format" - } - ], - "statusCode": 400 -} -``` - -**429 Too Many Requests** - Rate limit exceeded -```json -{ - "message": "Too many login attempts. Please try again later.", - "statusCode": 429, - "retryAfter": 900 -} -``` - -**500 Internal Server Error** - Server error -```json -{ - "message": "Internal server error", - "statusCode": 500 -} -``` - -## Frontend Behavior - -### 1. Form Validation -- Email: Required, valid email format -- Password: Required, minimum 8 characters -- Show/hide password toggle - -### 2. Loading State -- Disable form during submission -- Show "Logging in..." button text -- Prevent multiple submissions - -### 3. Success Flow -1. Validate response contains user data -2. Check if user.role === 'ADMIN' -3. Store access_token (if provided) -4. Store user data in localStorage -5. Show success toast -6. Redirect to dashboard or original destination - -### 4. Error Handling -- Display user-friendly error messages -- Show toast notification -- Keep form enabled for retry -- Don't expose sensitive error details - -### 5. Security Features -- HTTPS only in production -- httpOnly cookies support -- CSRF protection (SameSite cookies) -- Automatic token refresh -- Role-based access control - -## Backend Requirements - -### Must Implement -1. **Password Hashing**: bcrypt with salt rounds >= 12 -2. **Rate Limiting**: 5 attempts per 15 minutes per IP -3. **Account Lockout**: Lock after 5 failed attempts -4. **Role Verification**: Ensure user.role === 'ADMIN' -5. **Active Status Check**: Verify user.isActive === true -6. **Token Generation**: JWT with proper expiration -7. **Audit Logging**: Log all login attempts - -### Recommended -1. **httpOnly Cookies**: Store tokens in cookies, not response body -2. **Refresh Tokens**: Long-lived tokens for session renewal -3. **2FA Support**: Two-factor authentication -4. **IP Whitelisting**: Restrict admin access by IP -5. **Session Management**: Track active sessions -6. **Email Notifications**: Alert on new login - -## Testing - -### Manual Testing -```bash -# Test with valid credentials -curl -X POST http://localhost:3000/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@example.com","password":"password123"}' - -# Test with invalid credentials -curl -X POST http://localhost:3000/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@example.com","password":"wrong"}' - -# Test with non-admin user -curl -X POST http://localhost:3000/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"user@example.com","password":"password123"}' -``` - -### Automated Testing -See `src/pages/login/__tests__/index.test.tsx` for component tests. - -## Environment Variables - -### Development (`.env`) -```env -VITE_BACKEND_API_URL=http://localhost:3000/api/v1 -``` - -### Production (`.env.production`) -```env -VITE_BACKEND_API_URL=https://api.yourdomain.com/api/v1 -``` - -## API Client Configuration - -The login endpoint uses the `publicApi` instance which: -- Does NOT require authentication -- Does NOT send Authorization header -- DOES send cookies (`withCredentials: true`) -- DOES handle CORS properly - -## Flow Diagram - -``` -User enters credentials - ↓ -Form validation - ↓ -POST /api/v1/auth/login - ↓ -Backend validates credentials - ↓ -Backend checks role === 'ADMIN' - ↓ -Backend generates tokens - ↓ -Backend returns user + tokens - ↓ -Frontend checks role === 'ADMIN' - ↓ -Frontend stores tokens - ↓ -Frontend redirects to dashboard -``` - -## Security Checklist - -Backend: -- [ ] Passwords hashed with bcrypt -- [ ] Rate limiting enabled -- [ ] Account lockout implemented -- [ ] HTTPS enforced -- [ ] CORS configured properly -- [ ] httpOnly cookies used -- [ ] Audit logging enabled -- [ ] Input validation -- [ ] SQL injection prevention - -Frontend: -- [x] HTTPS only in production -- [x] Cookie support enabled -- [x] Role verification -- [x] Error handling -- [x] Loading states -- [x] Form validation -- [x] Token storage -- [x] Automatic token refresh - -## Troubleshooting - -### Issue: "Network Error" -**Solution:** Check API URL in environment variables - -### Issue: "CORS Error" -**Solution:** Backend must allow credentials and specific origin - -### Issue: "Invalid credentials" for valid user -**Solution:** Check backend password hashing and comparison - -### Issue: "Access denied" for admin user -**Solution:** Verify user.role === 'ADMIN' in database - -### Issue: Token not persisting -**Solution:** Check if backend is setting httpOnly cookies or returning access_token - -## Related Documentation - -- [Authentication Setup](./AUTHENTICATION.md) -- [API Standards](./API_STANDARDS.md) -- [Security Checklist](./SECURITY_CHECKLIST.md) -- [Backend Requirements](./SECURITY_CHECKLIST.md#backend-security) - -## Support - -For issues with login: -1. Check browser console for errors -2. Check network tab for API response -3. Verify environment variables -4. Check backend logs -5. Test with curl/Postman - diff --git a/dev-docs/PRE_DEPLOYMENT_CHECKLIST.md b/dev-docs/PRE_DEPLOYMENT_CHECKLIST.md deleted file mode 100644 index 8cfce94..0000000 --- a/dev-docs/PRE_DEPLOYMENT_CHECKLIST.md +++ /dev/null @@ -1,203 +0,0 @@ -# Pre-Deployment Checklist - -Use this checklist before deploying to production. - -## ✅ Code Quality - -- [x] All TypeScript errors resolved -- [x] Build completes successfully (`npm run build`) -- [x] Type checking passes (`npm run type-check`) -- [ ] ESLint warnings addressed (`npm run lint`) -- [ ] No console.log statements in production code -- [ ] All TODO comments resolved or documented - -## ✅ Environment Setup - -- [ ] `.env.production` file created -- [ ] `VITE_API_URL` set to production API endpoint -- [ ] Backend API is accessible from production domain -- [ ] CORS configured on backend for production domain -- [ ] All required environment variables documented - -## ✅ Security - -- [ ] HTTPS/SSL certificate obtained and configured -- [ ] Security headers configured (see nginx.conf or hosting config) -- [ ] API endpoints secured with authentication -- [ ] Sensitive data not exposed in client code -- [ ] Rate limiting configured on backend -- [ ] Error messages don't expose sensitive information -- [ ] Dependencies audited (`npm audit`) - -## ✅ Testing - -- [ ] Application tested in development mode -- [ ] Production build tested locally (`npm run preview`) -- [ ] Login/logout flow tested -- [ ] All main routes tested -- [ ] API calls tested and working -- [ ] Error handling tested (network errors, 401, 403, 404, 500) -- [ ] Mobile responsiveness verified -- [ ] Cross-browser testing completed: - - [ ] Chrome - - [ ] Firefox - - [ ] Safari - - [ ] Edge - -## ✅ Performance - -- [ ] Bundle size reviewed (should be ~970 KB uncompressed) -- [ ] Lighthouse performance score checked (aim for >80) -- [ ] Images optimized (if any) -- [ ] Code splitting configured (already done in vite.config.ts) -- [ ] Compression enabled on server (gzip/brotli) - -## ✅ Monitoring & Analytics - -- [ ] Error tracking service configured (Sentry, LogRocket, etc.) -- [ ] Analytics configured (Google Analytics, Plausible, etc.) -- [ ] Uptime monitoring set up -- [ ] Alert notifications configured -- [ ] Logging strategy defined - -## ✅ Documentation - -- [x] README.md updated with project info -- [x] Environment variables documented -- [x] Deployment instructions clear -- [ ] API documentation available -- [ ] Team trained on deployment process - -## ✅ Deployment Configuration - -Choose your deployment method and complete the relevant section: - -### For Vercel -- [ ] Vercel account created -- [ ] Project connected to repository -- [ ] Environment variables set in Vercel dashboard -- [ ] Custom domain configured (if applicable) -- [ ] Build command: `npm run build:prod` -- [ ] Output directory: `dist` - -### For Netlify -- [ ] Netlify account created -- [ ] Project connected to repository -- [ ] Environment variables set in Netlify dashboard -- [ ] Custom domain configured (if applicable) -- [ ] Build command: `npm run build:prod` -- [ ] Publish directory: `dist` - -### For Docker -- [ ] Docker image built successfully -- [ ] Container tested locally -- [ ] Image pushed to container registry -- [ ] Deployment platform configured (ECS, Cloud Run, etc.) -- [ ] Environment variables configured in platform -- [ ] Health checks configured - -### For VPS/Traditional Server -- [ ] Server provisioned and accessible -- [ ] Node.js 18+ installed -- [ ] Nginx installed and configured -- [ ] SSL certificate installed -- [ ] Firewall configured -- [ ] Automatic deployment script created - -## ✅ Post-Deployment - -After deploying, verify: - -- [ ] Application loads at production URL -- [ ] HTTPS working (no mixed content warnings) -- [ ] All routes accessible (test deep links) -- [ ] Login/authentication working -- [ ] API calls successful -- [ ] No console errors -- [ ] Error tracking receiving data -- [ ] Analytics tracking pageviews -- [ ] Performance acceptable (run Lighthouse) - -## ✅ Backup & Recovery - -- [ ] Previous version tagged in git -- [ ] Rollback procedure documented -- [ ] Database backup completed (if applicable) -- [ ] Configuration backed up - -## ✅ Communication - -- [ ] Stakeholders notified of deployment -- [ ] Maintenance window communicated (if applicable) -- [ ] Support team briefed -- [ ] Documentation shared with team - -## 🚨 Emergency Contacts - -Document your emergency contacts: - -- **Backend Team:** _________________ -- **DevOps/Infrastructure:** _________________ -- **Security Team:** _________________ -- **On-Call Engineer:** _________________ - -## 📋 Deployment Steps - -1. **Pre-deployment** - - [ ] Complete this checklist - - [ ] Create git tag: `git tag v1.0.0` - - [ ] Push tag: `git push origin v1.0.0` - -2. **Deployment** - - [ ] Deploy to staging first (if available) - - [ ] Test on staging - - [ ] Deploy to production - - [ ] Monitor for 15-30 minutes - -3. **Post-deployment** - - [ ] Verify application working - - [ ] Check error logs - - [ ] Monitor performance - - [ ] Notify stakeholders - -4. **If issues occur** - - [ ] Check error tracking service - - [ ] Review server logs - - [ ] Rollback if necessary - - [ ] Document issue for post-mortem - -## 📝 Deployment Log - -Keep a record of deployments: - -| Date | Version | Deployed By | Status | Notes | -|------|---------|-------------|--------|-------| -| YYYY-MM-DD | v1.0.0 | Name | ✅/❌ | Initial production release | - -## 🎯 Success Criteria - -Deployment is successful when: - -- ✅ Application loads without errors -- ✅ All critical features working -- ✅ No increase in error rate -- ✅ Performance within acceptable range -- ✅ No security vulnerabilities detected -- ✅ Monitoring and alerts active - -## 📞 Support - -If you encounter issues: - -1. Check `DEPLOYMENT.md` troubleshooting section -2. Review error logs in monitoring service -3. Check browser console for client-side errors -4. Verify API connectivity -5. Contact backend team if API issues -6. Rollback if critical issues persist - ---- - -**Remember:** It's better to delay deployment than to deploy with known issues. Take your time and verify each step. - -**Good luck with your deployment! 🚀** diff --git a/dev-docs/PRODUCTION_READY_SUMMARY.md b/dev-docs/PRODUCTION_READY_SUMMARY.md deleted file mode 100644 index f1146c9..0000000 --- a/dev-docs/PRODUCTION_READY_SUMMARY.md +++ /dev/null @@ -1,233 +0,0 @@ -# Production Ready Summary - -## ✅ Issues Fixed - -### 1. Build Errors (27 TypeScript errors) - FIXED -- Removed all unused imports across the codebase -- Fixed type safety issues in api-client.ts -- Added proper type annotations for error responses -- Fixed undefined variable references -- All files now compile successfully - -### 2. Environment Configuration - COMPLETED -- ✅ Created `.env.example` with all required variables -- ✅ Created `.env.production.example` for production setup -- ✅ Updated `.gitignore` to exclude environment files -- ✅ Documented all environment variables in README - -### 3. Documentation - COMPLETED -- ✅ Comprehensive README.md with: - - Project overview and features - - Installation instructions - - Development and production build steps - - Deployment guides for multiple platforms - - Environment variable documentation -- ✅ DEPLOYMENT.md with detailed deployment checklist -- ✅ SECURITY.md with security best practices -- ✅ This summary document - -### 4. Production Optimizations - COMPLETED -- ✅ Error boundary component for graceful error handling -- ✅ Code splitting configuration in vite.config.ts -- ✅ Manual chunks for better caching (react, ui, charts, query) -- ✅ Build optimization settings -- ✅ Version updated to 1.0.0 - -### 5. Deployment Configuration - COMPLETED -- ✅ Dockerfile for containerized deployment -- ✅ nginx.conf with security headers and SPA routing -- ✅ vercel.json for Vercel deployment -- ✅ netlify.toml for Netlify deployment -- ✅ .dockerignore for efficient Docker builds -- ✅ GitHub Actions CI workflow - -### 6. Security Improvements - COMPLETED -- ✅ Security headers configured (X-Frame-Options, CSP, etc.) -- ✅ Error boundary prevents app crashes -- ✅ Comprehensive security documentation -- ✅ Security best practices guide -- ⚠️ Token storage still uses localStorage (documented for improvement) - -## 📊 Build Status - -``` -✓ TypeScript compilation: SUCCESS -✓ Vite build: SUCCESS -✓ Bundle size: Optimized with code splitting -✓ No critical warnings -``` - -### Build Output -- Total bundle size: ~970 KB (before gzip) -- Gzipped size: ~288 KB -- Code split into 6 chunks for optimal caching - -## 📁 New Files Created - -### Configuration Files -- `.env.example` - Development environment template -- `.env.production.example` - Production environment template -- `vite.config.ts` - Updated with production optimizations -- `vercel.json` - Vercel deployment configuration -- `netlify.toml` - Netlify deployment configuration -- `Dockerfile` - Docker containerization -- `nginx.conf` - Nginx server configuration -- `.dockerignore` - Docker build optimization -- `.github/workflows/ci.yml` - CI/CD pipeline - -### Documentation -- `README.md` - Comprehensive project documentation -- `DEPLOYMENT.md` - Deployment guide and checklist -- `SECURITY.md` - Security best practices -- `PRODUCTION_READY_SUMMARY.md` - This file - -### Components -- `src/components/ErrorBoundary.tsx` - Error boundary component - -## 🚀 Quick Start for Production - -### 1. Set Up Environment -```bash -cp .env.production.example .env.production -# Edit .env.production with your production API URL -``` - -### 2. Build -```bash -npm run build:prod -``` - -### 3. Test Locally -```bash -npm run preview -``` - -### 4. Deploy -Choose your platform: -- **Vercel:** `vercel --prod` -- **Netlify:** `netlify deploy --prod` -- **Docker:** `docker build -t yaltopia-admin . && docker run -p 80:80 yaltopia-admin` - -## ⚠️ Important Notes Before Production - -### Must Do -1. **Set up HTTPS** - Never deploy without SSL/TLS -2. **Configure environment variables** - Set VITE_API_URL to production API -3. **Test authentication flow** - Ensure login/logout works -4. **Verify API connectivity** - Test all API endpoints -5. **Configure CORS** - Backend must allow your production domain - -### Should Do -1. **Set up error tracking** - Sentry, LogRocket, or similar -2. **Configure analytics** - Google Analytics, Plausible, etc. -3. **Set up monitoring** - Uptime monitoring and alerts -4. **Review security checklist** - See SECURITY.md -5. **Test on multiple browsers** - Chrome, Firefox, Safari, Edge - -### Consider Doing -1. **Implement httpOnly cookies** - More secure than localStorage -2. **Add rate limiting** - Protect against abuse -3. **Set up CDN** - Cloudflare, AWS CloudFront, etc. -4. **Enable compression** - Gzip/Brotli on server -5. **Add CSP headers** - Content Security Policy - -## 🔒 Security Status - -### Implemented ✅ -- Error boundary for graceful failures -- Security headers in deployment configs -- HTTPS enforcement in configs -- Input validation on forms -- Error handling for API calls -- Environment variable management - -### Recommended Improvements ⚠️ -- Move from localStorage to httpOnly cookies for tokens -- Implement Content Security Policy (CSP) -- Add rate limiting on backend -- Set up error tracking service -- Implement session timeout -- Add security monitoring - -See `SECURITY.md` for detailed security recommendations. - -## 📈 Performance - -### Current Status -- Bundle split into 6 optimized chunks -- React vendor: 47 KB (gzipped: 17 KB) -- UI vendor: 107 KB (gzipped: 32 KB) -- Chart vendor: 383 KB (gzipped: 112 KB) -- Main app: 396 KB (gzipped: 117 KB) - -### Optimization Opportunities -- Lazy load routes (if needed) -- Optimize images (if any large images added) -- Consider removing unused Radix UI components -- Implement virtual scrolling for large tables - -## 🧪 Testing Checklist - -Before deploying to production: - -- [ ] Build completes without errors -- [ ] Application loads in browser -- [ ] Login/authentication works -- [ ] All routes accessible -- [ ] API calls successful -- [ ] Error handling works -- [ ] No console errors -- [ ] Mobile responsive -- [ ] Cross-browser compatible -- [ ] Performance acceptable (Lighthouse score) - -## 📞 Support & Maintenance - -### Regular Tasks -- **Daily:** Monitor error logs -- **Weekly:** Review security alerts, check for updates -- **Monthly:** Run `npm audit`, update dependencies -- **Quarterly:** Security review, performance audit - -### Troubleshooting -See `DEPLOYMENT.md` for common issues and solutions. - -## 🎯 Next Steps - -1. **Immediate:** - - Set up production environment variables - - Deploy to staging environment - - Run full test suite - - Deploy to production - -2. **Short-term (1-2 weeks):** - - Set up error tracking (Sentry) - - Configure analytics - - Set up monitoring and alerts - - Implement security improvements - -3. **Long-term (1-3 months):** - - Add automated testing - - Implement CI/CD pipeline - - Performance optimization - - Security audit - -## ✨ Summary - -Your Yaltopia Ticket Admin application is now **production-ready** with: - -- ✅ All TypeScript errors fixed -- ✅ Build successfully compiling -- ✅ Comprehensive documentation -- ✅ Multiple deployment options configured -- ✅ Security best practices documented -- ✅ Error handling implemented -- ✅ Production optimizations applied - -**The application can be deployed to production**, but review the security recommendations and complete the pre-deployment checklist in `DEPLOYMENT.md` for best results. - ---- - -**Version:** 1.0.0 -**Last Updated:** February 24, 2026 -**Status:** ✅ Production Ready diff --git a/dev-docs/QUICK_REFERENCE.md b/dev-docs/QUICK_REFERENCE.md deleted file mode 100644 index 0e81535..0000000 --- a/dev-docs/QUICK_REFERENCE.md +++ /dev/null @@ -1,206 +0,0 @@ -# Quick Reference Guide - -## Common Commands - -```bash -# Development -npm run dev # Start dev server (http://localhost:5173) -npm run build # Build for production -npm run build:prod # Build with production env -npm run preview # Preview production build -npm run lint # Run ESLint -npm run lint:fix # Fix ESLint errors -npm run type-check # TypeScript type checking - -# Deployment -vercel --prod # Deploy to Vercel -netlify deploy --prod # Deploy to Netlify -docker build -t app . # Build Docker image -docker run -p 80:80 app # Run Docker container -``` - -## Environment Variables - -```env -# Required -VITE_API_URL=http://localhost:3000/api/v1 - -# Optional -VITE_ENV=development -VITE_ANALYTICS_ID= -VITE_SENTRY_DSN= -``` - -## File Structure - -``` -├── src/ -│ ├── app/ # App config (query client) -│ ├── components/ # Reusable components -│ │ └── ui/ # UI components -│ ├── layouts/ # Layout components -│ ├── lib/ # Utils & API client -│ ├── pages/ # Page components -│ │ └── admin/ # Admin pages -│ ├── App.tsx # Main app -│ └── main.tsx # Entry point -├── .env.example # Env template -├── vite.config.ts # Vite config -├── package.json # Dependencies -└── README.md # Documentation -``` - -## Key Files - -| File | Purpose | -|------|---------| -| `src/lib/api-client.ts` | API configuration & helpers | -| `src/app/query-client.ts` | React Query setup | -| `src/components/ErrorBoundary.tsx` | Error handling | -| `vite.config.ts` | Build configuration | -| `.env.example` | Environment template | - -## API Client Usage - -```typescript -import { adminApiHelpers } from '@/lib/api-client'; - -// Get users -const response = await adminApiHelpers.getUsers({ page: 1, limit: 20 }); - -// Get user by ID -const user = await adminApiHelpers.getUser(userId); - -// Update user -await adminApiHelpers.updateUser(userId, { isActive: false }); - -// Delete user -await adminApiHelpers.deleteUser(userId); -``` - -## React Query Usage - -```typescript -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; - -// Fetch data -const { data, isLoading, error } = useQuery({ - queryKey: ['users', page], - queryFn: async () => { - const response = await adminApiHelpers.getUsers({ page }); - return response.data; - }, -}); - -// Mutate data -const mutation = useMutation({ - mutationFn: async (data) => { - await adminApiHelpers.updateUser(id, data); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['users'] }); - }, -}); -``` - -## Routing - -```typescript -import { useNavigate, useParams } from 'react-router-dom'; - -// Navigate -const navigate = useNavigate(); -navigate('/admin/users'); - -// Get params -const { id } = useParams(); -``` - -## Toast Notifications - -```typescript -import { toast } from 'sonner'; - -toast.success('Success message'); -toast.error('Error message'); -toast.info('Info message'); -toast.warning('Warning message'); -``` - -## Deployment Quick Start - -### Vercel -```bash -npm i -g vercel -vercel login -vercel --prod -``` - -### Netlify -```bash -npm i -g netlify-cli -netlify login -netlify deploy --prod -``` - -### Docker -```bash -docker build -t yaltopia-admin . -docker run -p 80:80 yaltopia-admin -``` - -## Troubleshooting - -### Build fails -```bash -rm -rf node_modules package-lock.json -npm install -npm run build -``` - -### Type errors -```bash -npm run type-check -``` - -### Blank page after deploy -- Check browser console -- Verify API URL in env vars -- Check server config for SPA routing - -### API calls failing -- Check CORS on backend -- Verify API URL -- Check network tab in DevTools - -## Security Checklist - -- [ ] HTTPS enabled -- [ ] Environment variables set -- [ ] CORS configured -- [ ] Security headers added -- [ ] Error tracking set up -- [ ] Monitoring configured - -## Performance Tips - -- Use code splitting for large routes -- Lazy load heavy components -- Optimize images -- Enable compression (gzip/brotli) -- Use CDN for static assets - -## Useful Links - -- [React Query Docs](https://tanstack.com/query/latest) -- [React Router Docs](https://reactrouter.com/) -- [Vite Docs](https://vitejs.dev/) -- [Tailwind CSS Docs](https://tailwindcss.com/) -- [Radix UI Docs](https://www.radix-ui.com/) - -## Support - -- Check `README.md` for detailed docs -- See `DEPLOYMENT.md` for deployment guide -- Review `SECURITY.md` for security best practices -- Read `PRODUCTION_READY_SUMMARY.md` for status diff --git a/dev-docs/README.md b/dev-docs/README.md index f9e7c73..0a17e1e 100644 --- a/dev-docs/README.md +++ b/dev-docs/README.md @@ -1,91 +1,101 @@ # Developer Documentation -This directory contains comprehensive documentation for the Yaltopia Ticket Admin project. +Essential documentation for the Yaltopia Ticket Admin project. -## 📚 Documentation Index +## 📚 Documentation -### Getting Started -- **[Quick Reference](./QUICK_REFERENCE.md)** - Quick start guide and common commands -- **[Tech Stack](./TECH_STACK.md)** - Technologies and frameworks used +### [Development Guide](./DEVELOPMENT.md) +Complete development guide including: +- Tech stack & project structure +- Quick start & setup +- Common tasks & best practices +- Troubleshooting -### Development -- **[Authentication](./AUTHENTICATION.md)** - Authentication setup and flow -- **[API Standards](./API_STANDARDS.md)** - API client implementation and best practices -- **[Login API Documentation](./LOGIN_API_DOCUMENTATION.md)** - Login endpoint specifications -- **[Troubleshooting](./TROUBLESHOOTING.md)** - Common issues and solutions +### [API & Service Layer Guide](./API_GUIDE.md) ⭐ +**Essential reading for making API calls:** +- Service layer architecture +- All available services & methods +- Common patterns & examples +- Error handling +- Best practices -### Testing & Quality -- **[Testing Guide](./TESTING_GUIDE.md)** - Testing setup and best practices -- **[CI/CD Setup](./CI_CD_SETUP.md)** - Continuous integration and deployment -- **[Error Monitoring](./ERROR_MONITORING.md)** - Sentry integration and error tracking +### [Testing Guide](./TESTING_GUIDE.md) +Testing setup and practices: +- Unit testing with Vitest +- Component testing +- Integration testing +- Test utilities & mocks -### Security -- **[Security Checklist](./SECURITY_CHECKLIST.md)** - Comprehensive security requirements -- **[Security](./SECURITY.md)** - Security best practices and guidelines +### [Deployment Guide](./DEPLOYMENT.md) +Production deployment: +- Pre-deployment checklist +- Deployment options (Vercel, Netlify, Docker) +- Environment configuration +- CI/CD setup -### Deployment -- **[Deployment Options](./DEPLOYMENT_OPTIONS.md)** - All deployment configurations (Vercel, Netlify, Docker) -- **[Deployment Guide](./DEPLOYMENT.md)** - Step-by-step deployment instructions -- **[Pre-Deployment Checklist](./PRE_DEPLOYMENT_CHECKLIST.md)** - Checklist before going live -- **[Production Ready Summary](./PRODUCTION_READY_SUMMARY.md)** - Production readiness overview +### [Security Guide](./SECURITY.md) +Security best practices: +- Authentication & authorization +- Data protection +- Security headers +- CORS configuration +- Input validation -## 🎯 Quick Links +## 🚀 Quick Start -### For Developers -1. Start with [Quick Reference](./QUICK_REFERENCE.md) -2. Understand [Tech Stack](./TECH_STACK.md) -3. Set up [Authentication](./AUTHENTICATION.md) -4. Review [API Standards](./API_STANDARDS.md) +```bash +# Install +npm install -### For DevOps -1. Review [CI/CD Setup](./CI_CD_SETUP.md) -2. Choose deployment from [Deployment Options](./DEPLOYMENT_OPTIONS.md) -3. Follow [Deployment Guide](./DEPLOYMENT.md) -4. Complete [Pre-Deployment Checklist](./PRE_DEPLOYMENT_CHECKLIST.md) +# Configure +cp .env.example .env +# Edit .env with your backend URL -### For Security Review -1. Review [Security Checklist](./SECURITY_CHECKLIST.md) -2. Check [Security](./SECURITY.md) guidelines -3. Verify [API Standards](./API_STANDARDS.md) compliance +# Develop +npm run dev -### For Troubleshooting -1. Check [Troubleshooting](./TROUBLESHOOTING.md) guide -2. Review [Error Monitoring](./ERROR_MONITORING.md) setup -3. Consult [API Standards](./API_STANDARDS.md) for API issues +# Test +npm run test -## 📖 Documentation Standards +# Build +npm run build +``` -All documentation follows these principles: -- **Clear and Concise** - Easy to understand -- **Actionable** - Includes examples and commands -- **Up-to-date** - Reflects current implementation -- **Professional** - Industry-standard practices +## 📖 Key Concepts -## 🔄 Keeping Documentation Updated +### Service Layer +All API calls go through typed service classes: +```typescript +import { userService } from '@/services' +const users = await userService.getUsers() +``` -When making changes to the project: -1. Update relevant documentation -2. Add new sections if needed -3. Remove outdated information -4. Keep examples current +### React Query +Data fetching with caching: +```typescript +const { data } = useQuery({ + queryKey: ['users'], + queryFn: () => userService.getUsers() +}) +``` -## 📝 Contributing to Documentation - -To improve documentation: -1. Identify gaps or unclear sections -2. Add examples and use cases -3. Include troubleshooting tips -4. Keep formatting consistent +### Protected Routes +Authentication required for admin routes: +```typescript +}> + } /> + +``` ## 🆘 Need Help? -If documentation is unclear or missing: -1. Check [Troubleshooting](./TROUBLESHOOTING.md) -2. Review related documentation -3. Check code comments -4. Consult team members +1. **Making API calls?** → [API & Service Layer Guide](./API_GUIDE.md) +2. **General development?** → [Development Guide](./DEVELOPMENT.md) +3. **Writing tests?** → [Testing Guide](./TESTING_GUIDE.md) +4. **Deploying?** → [Deployment Guide](./DEPLOYMENT.md) +5. **Security questions?** → [Security Guide](./SECURITY.md) --- -**Last Updated:** 2024 +**Last Updated:** 2024 **Maintained By:** Development Team diff --git a/dev-docs/SECURITY_CHECKLIST.md b/dev-docs/SECURITY_CHECKLIST.md deleted file mode 100644 index a825707..0000000 --- a/dev-docs/SECURITY_CHECKLIST.md +++ /dev/null @@ -1,406 +0,0 @@ -# Security Checklist - -## Frontend Security (✅ Implemented) - -### Authentication & Authorization -- ✅ **Protected Routes**: All admin routes require authentication -- ✅ **Role-Based Access**: Checks for ADMIN role before granting access -- ✅ **Cookie Support**: `withCredentials: true` for httpOnly cookies -- ✅ **Token Refresh**: Automatic token refresh on 401 errors -- ✅ **Centralized Logout**: Calls backend to clear cookies -- ✅ **Secure Redirects**: Prevents redirect loops on login page -- ✅ **localStorage Fallback**: Works with backends without cookie support - -### API Security -- ✅ **Separate API Instances**: Public vs authenticated endpoints -- ✅ **Bearer Token**: Proper Authorization header format -- ✅ **Error Handling**: Consistent error responses with user feedback -- ✅ **Request Retry**: Automatic retry after token refresh -- ✅ **CORS Credentials**: Enabled for cross-origin cookie sharing - -### Code Security -- ✅ **TypeScript**: Type safety throughout the application -- ✅ **Input Validation**: Form validation on login -- ✅ **Error Messages**: Generic error messages (no sensitive info leak) -- ✅ **No Hardcoded Secrets**: Uses environment variables - -## Backend Security (⚠️ Must Implement) - -### Critical Requirements - -#### 1. httpOnly Cookies (Recommended) -```javascript -// ⚠️ BACKEND MUST IMPLEMENT -res.cookie('access_token', token, { - httpOnly: true, // ✅ Prevents XSS attacks - secure: true, // ✅ HTTPS only (production) - sameSite: 'strict', // ✅ CSRF protection - maxAge: 900000, // ✅ 15 minutes - path: '/' -}) -``` - -**Why httpOnly?** -- Prevents JavaScript access to tokens -- Protects against XSS (Cross-Site Scripting) attacks -- Industry standard for authentication - -#### 2. Token Management -- ⚠️ **Short-lived Access Tokens**: 15 minutes max -- ⚠️ **Long-lived Refresh Tokens**: 7 days max -- ⚠️ **Token Rotation**: Generate new refresh token on each refresh -- ⚠️ **Token Revocation**: Invalidate tokens on logout -- ⚠️ **Token Blacklist**: Store revoked tokens (Redis recommended) - -#### 3. Password Security -- ⚠️ **Hashing**: Use bcrypt/argon2 (NOT MD5/SHA1) -- ⚠️ **Salt**: Unique salt per password -- ⚠️ **Cost Factor**: bcrypt rounds >= 12 -- ⚠️ **Password Policy**: Min 8 chars, complexity requirements - -```javascript -// ⚠️ BACKEND MUST IMPLEMENT -const bcrypt = require('bcrypt') -const saltRounds = 12 -const hashedPassword = await bcrypt.hash(password, saltRounds) -``` - -#### 4. Rate Limiting -- ⚠️ **Login Endpoint**: 5 attempts per 15 minutes per IP -- ⚠️ **API Endpoints**: 100 requests per minute per user -- ⚠️ **Account Lockout**: Lock after 5 failed login attempts - -```javascript -// ⚠️ BACKEND MUST IMPLEMENT -const rateLimit = require('express-rate-limit') - -const loginLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 5, // 5 requests per window - message: 'Too many login attempts, please try again later' -}) - -app.post('/auth/login', loginLimiter, loginHandler) -``` - -#### 5. CORS Configuration -```javascript -// ⚠️ BACKEND MUST IMPLEMENT -app.use(cors({ - origin: process.env.FRONTEND_URL, // Specific origin, not '*' - credentials: true, // Allow cookies - methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], - allowedHeaders: ['Content-Type', 'Authorization'] -})) -``` - -#### 6. Input Validation -- ⚠️ **Sanitize Inputs**: Prevent SQL injection, XSS -- ⚠️ **Validate Email**: Proper email format -- ⚠️ **Validate Types**: Check data types -- ⚠️ **Limit Payload Size**: Prevent DoS attacks - -```javascript -// ⚠️ BACKEND MUST IMPLEMENT -const { body, validationResult } = require('express-validator') - -app.post('/auth/login', [ - body('email').isEmail().normalizeEmail(), - body('password').isLength({ min: 8 }) -], (req, res) => { - const errors = validationResult(req) - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }) - } - // Process login -}) -``` - -#### 7. SQL Injection Prevention -- ⚠️ **Parameterized Queries**: Use prepared statements -- ⚠️ **ORM**: Use Prisma, TypeORM, Sequelize -- ⚠️ **Never**: Concatenate user input into SQL - -```javascript -// ❌ VULNERABLE -const query = `SELECT * FROM users WHERE email = '${email}'` - -// ✅ SAFE -const query = 'SELECT * FROM users WHERE email = ?' -db.query(query, [email]) -``` - -#### 8. XSS Prevention -- ⚠️ **Escape Output**: Sanitize data before rendering -- ⚠️ **Content Security Policy**: Set CSP headers -- ⚠️ **httpOnly Cookies**: Prevent JavaScript access to tokens - -```javascript -// ⚠️ BACKEND MUST IMPLEMENT -const helmet = require('helmet') -app.use(helmet.contentSecurityPolicy({ - directives: { - defaultSrc: ["'self'"], - scriptSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'"], - imgSrc: ["'self'", "data:", "https:"], - } -})) -``` - -#### 9. HTTPS/TLS -- ⚠️ **Production**: HTTPS only (no HTTP) -- ⚠️ **TLS 1.2+**: Disable older versions -- ⚠️ **HSTS Header**: Force HTTPS - -```javascript -// ⚠️ BACKEND MUST IMPLEMENT -app.use(helmet.hsts({ - maxAge: 31536000, // 1 year - includeSubDomains: true, - preload: true -})) -``` - -#### 10. Security Headers -```javascript -// ⚠️ BACKEND MUST IMPLEMENT -const helmet = require('helmet') -app.use(helmet()) // Sets multiple security headers - -// Or manually: -app.use((req, res, next) => { - res.setHeader('X-Content-Type-Options', 'nosniff') - res.setHeader('X-Frame-Options', 'DENY') - res.setHeader('X-XSS-Protection', '1; mode=block') - res.setHeader('Strict-Transport-Security', 'max-age=31536000') - next() -}) -``` - -#### 11. Audit Logging -- ⚠️ **Log All Admin Actions**: Who, what, when, where -- ⚠️ **Log Failed Logins**: Track suspicious activity -- ⚠️ **Log Sensitive Operations**: User deletion, role changes -- ⚠️ **Secure Logs**: Store in separate database/service - -```javascript -// ⚠️ BACKEND MUST IMPLEMENT -const auditLog = async (userId, action, resource, details) => { - await db.auditLogs.create({ - userId, - action, - resource, - details, - ipAddress: req.ip, - userAgent: req.headers['user-agent'], - timestamp: new Date() - }) -} -``` - -#### 12. Database Security -- ⚠️ **Least Privilege**: Database user with minimal permissions -- ⚠️ **Encrypted Connections**: Use SSL/TLS for database -- ⚠️ **Backup Encryption**: Encrypt database backups -- ⚠️ **Sensitive Data**: Encrypt PII at rest - -#### 13. Environment Variables -- ⚠️ **Never Commit**: .env files in .gitignore -- ⚠️ **Secrets Management**: Use vault (AWS Secrets Manager, etc.) -- ⚠️ **Rotate Secrets**: Regular rotation of API keys, tokens - -```bash -# ⚠️ BACKEND MUST CONFIGURE -JWT_SECRET= -JWT_REFRESH_SECRET= -DATABASE_URL= -FRONTEND_URL=https://admin.yourdomain.com -NODE_ENV=production -``` - -#### 14. Session Management -- ⚠️ **Session Timeout**: Auto-logout after inactivity -- ⚠️ **Concurrent Sessions**: Limit or track multiple sessions -- ⚠️ **Session Invalidation**: Clear on logout, password change - -## Additional Security Measures - -### Frontend (Optional Improvements) - -#### 1. Content Security Policy (CSP) -```html - - -``` - -#### 2. Subresource Integrity (SRI) -```html - - -``` - -#### 3. Input Sanitization -```typescript -// Install DOMPurify -import DOMPurify from 'dompurify' - -const sanitizedInput = DOMPurify.sanitize(userInput) -``` - -#### 4. Two-Factor Authentication (2FA) -- Add TOTP support (Google Authenticator) -- SMS verification -- Backup codes - -#### 5. Password Strength Meter -```typescript -// Install zxcvbn -import zxcvbn from 'zxcvbn' - -const strength = zxcvbn(password) -// Show strength indicator to user -``` - -### Backend (Additional) - -#### 1. API Versioning -```javascript -app.use('/api/v1', v1Routes) -app.use('/api/v2', v2Routes) -``` - -#### 2. Request Signing -- Sign requests with HMAC -- Verify signature on backend -- Prevents request tampering - -#### 3. IP Whitelisting (Admin Panel) -```javascript -const adminIpWhitelist = ['192.168.1.1', '10.0.0.1'] - -const ipWhitelistMiddleware = (req, res, next) => { - if (!adminIpWhitelist.includes(req.ip)) { - return res.status(403).json({ message: 'Access denied' }) - } - next() -} - -app.use('/admin', ipWhitelistMiddleware) -``` - -#### 4. Geo-blocking -- Block requests from certain countries -- Use CloudFlare or similar service - -#### 5. DDoS Protection -- Use CloudFlare, AWS Shield -- Rate limiting at infrastructure level -- CDN for static assets - -## Security Testing - -### Automated Testing -- ⚠️ **OWASP ZAP**: Automated security scanning -- ⚠️ **npm audit**: Check for vulnerable dependencies -- ⚠️ **Snyk**: Continuous security monitoring -- ⚠️ **SonarQube**: Code quality and security - -```bash -# Run security audit -npm audit -npm audit fix - -# Check for outdated packages -npm outdated -``` - -### Manual Testing -- ⚠️ **Penetration Testing**: Hire security experts -- ⚠️ **Code Review**: Security-focused code reviews -- ⚠️ **Vulnerability Scanning**: Regular scans - -## Compliance - -### GDPR (EU) -- ⚠️ **Data Minimization**: Collect only necessary data -- ⚠️ **Right to Erasure**: Allow users to delete their data -- ⚠️ **Data Portability**: Export user data -- ⚠️ **Consent**: Explicit consent for data processing -- ⚠️ **Privacy Policy**: Clear privacy policy - -### HIPAA (Healthcare - US) -- ⚠️ **Encryption**: Encrypt PHI at rest and in transit -- ⚠️ **Access Controls**: Role-based access -- ⚠️ **Audit Logs**: Track all PHI access -- ⚠️ **Business Associate Agreement**: With third parties - -### PCI DSS (Payment Cards) -- ⚠️ **Never Store**: CVV, full card numbers -- ⚠️ **Tokenization**: Use payment gateway tokens -- ⚠️ **Encryption**: Encrypt cardholder data - -## Monitoring & Alerting - -### What to Monitor -- ⚠️ **Failed Login Attempts**: Alert on threshold -- ⚠️ **Unusual Activity**: Large data exports, bulk deletions -- ⚠️ **API Errors**: Spike in 401/403/500 errors -- ⚠️ **Performance**: Slow queries, high CPU -- ⚠️ **Security Events**: Unauthorized access attempts - -### Tools -- **Sentry**: Error tracking -- **DataDog**: Application monitoring -- **CloudWatch**: AWS monitoring -- **Prometheus + Grafana**: Metrics and dashboards - -## Incident Response Plan - -### Steps -1. **Detect**: Identify security incident -2. **Contain**: Isolate affected systems -3. **Investigate**: Determine scope and impact -4. **Remediate**: Fix vulnerability -5. **Recover**: Restore normal operations -6. **Review**: Post-incident analysis - -### Contacts -- Security team email -- On-call engineer -- Legal team (for data breaches) -- PR team (for public disclosure) - -## Summary - -### Current Status -✅ **Frontend**: Implements industry-standard security patterns -⚠️ **Backend**: Must implement security measures listed above - -### Priority Actions (Backend) -1. 🔴 **Critical**: Implement httpOnly cookies -2. 🔴 **Critical**: Hash passwords with bcrypt -3. 🔴 **Critical**: Add rate limiting -4. 🔴 **Critical**: Enable HTTPS in production -5. 🟡 **High**: Implement token refresh -6. 🟡 **High**: Add input validation -7. 🟡 **High**: Configure CORS properly -8. 🟡 **High**: Add security headers -9. 🟢 **Medium**: Implement audit logging -10. 🟢 **Medium**: Add 2FA support - -### Security Score -- **Frontend**: 9/10 ✅ -- **Backend**: Depends on implementation ⚠️ -- **Overall**: Requires backend security implementation - -### Next Steps -1. Review this checklist with backend team -2. Implement critical security measures -3. Conduct security audit -4. Set up monitoring and alerting -5. Create incident response plan -6. Regular security reviews and updates diff --git a/dev-docs/TECH_STACK.md b/dev-docs/TECH_STACK.md deleted file mode 100644 index 7c2e0b4..0000000 --- a/dev-docs/TECH_STACK.md +++ /dev/null @@ -1,437 +0,0 @@ -# Tech Stack & Frameworks - -## Project Overview -**Yaltopia Ticket Admin** - Admin dashboard for ticket management system - -## Core Technologies - -### Frontend Framework -- **React 19.2.0** - Latest version with modern features - - Component-based architecture - - Hooks for state management - - Concurrent rendering - - Automatic batching - -### Language -- **TypeScript 5.9.3** - Type-safe JavaScript - - Static type checking - - Enhanced IDE support - - Better code documentation - - Reduced runtime errors - -### Build Tool -- **Vite 7.2.4** - Next-generation frontend tooling - - Lightning-fast HMR (Hot Module Replacement) - - Optimized production builds - - Native ES modules - - Plugin ecosystem - - Code splitting and lazy loading - -## UI & Styling - -### CSS Framework -- **Tailwind CSS 3.4.17** - Utility-first CSS framework - - Rapid UI development - - Consistent design system - - Responsive design utilities - - Dark mode support - - Custom theme configuration - -### Component Library -- **Radix UI** - Unstyled, accessible component primitives - - `@radix-ui/react-avatar` - Avatar component - - `@radix-ui/react-dialog` - Modal dialogs - - `@radix-ui/react-dropdown-menu` - Dropdown menus - - `@radix-ui/react-label` - Form labels - - `@radix-ui/react-scroll-area` - Custom scrollbars - - `@radix-ui/react-select` - Select dropdowns - - `@radix-ui/react-separator` - Visual separators - - `@radix-ui/react-slot` - Composition utility - - `@radix-ui/react-switch` - Toggle switches - - `@radix-ui/react-tabs` - Tab navigation - - `@radix-ui/react-toast` - Toast notifications - -**Why Radix UI?** -- Fully accessible (WCAG compliant) -- Unstyled (full design control) -- Keyboard navigation -- Focus management -- Screen reader support - -### UI Utilities -- **class-variance-authority (CVA)** - Component variant management -- **clsx** - Conditional className utility -- **tailwind-merge** - Merge Tailwind classes intelligently -- **tailwindcss-animate** - Animation utilities - -### Icons -- **Lucide React 0.561.0** - Beautiful, consistent icon set - - 1000+ icons - - Tree-shakeable - - Customizable size and color - - Accessible - -## Routing - -### Router -- **React Router v7.11.0** - Declarative routing - - Nested routes - - Protected routes - - Dynamic routing - - Navigation guards - - Location state management - -## State Management - -### Server State -- **TanStack Query (React Query) 5.90.12** - Powerful data synchronization - - Automatic caching - - Background refetching - - Optimistic updates - - Pagination support - - Infinite queries - - Devtools for debugging - -**Why React Query?** -- Eliminates boilerplate for API calls -- Automatic loading/error states -- Smart caching and invalidation -- Reduces global state complexity - -### Local State -- **React Hooks** - Built-in state management - - `useState` - Component state - - `useEffect` - Side effects - - `useContext` - Context API - - Custom hooks for reusability - -## Data Fetching - -### HTTP Client -- **Axios 1.13.2** - Promise-based HTTP client - - Request/response interceptors - - Automatic JSON transformation - - Request cancellation - - Progress tracking - - Error handling - - TypeScript support - -**Features Implemented:** -- Automatic token injection -- Cookie support (`withCredentials`) -- Centralized error handling -- Automatic token refresh -- Request retry logic - -## Data Visualization - -### Charts -- **Recharts 3.6.0** - Composable charting library - - Line charts - - Bar charts - - Area charts - - Pie charts - - Responsive design - - Customizable styling - -**Used For:** -- User growth analytics -- Revenue trends -- API usage statistics -- Error rate monitoring -- Storage analytics - -## Utilities - -### Date Handling -- **date-fns 4.1.0** - Modern date utility library - - Lightweight (tree-shakeable) - - Immutable - - TypeScript support - - Timezone support - - Formatting and parsing - -### Notifications -- **Sonner 2.0.7** - Toast notification system - - Beautiful default styling - - Promise-based toasts - - Custom positioning - - Dismissible - - Accessible - -## Development Tools - -### Linting -- **ESLint 9.39.1** - JavaScript/TypeScript linter - - Code quality enforcement - - Best practices - - Error prevention - - Custom rules - -**Plugins:** -- `eslint-plugin-react-hooks` - React Hooks rules -- `eslint-plugin-react-refresh` - Fast Refresh rules -- `typescript-eslint` - TypeScript-specific rules - -### Build Tools -- **PostCSS 8.5.6** - CSS transformation -- **Autoprefixer 10.4.23** - Automatic vendor prefixes -- **TypeScript Compiler** - Type checking and transpilation - -### Type Definitions -- `@types/node` - Node.js types -- `@types/react` - React types -- `@types/react-dom` - React DOM types - -## Architecture Patterns - -### Design Patterns Used - -1. **Component Composition** - - Reusable UI components - - Props-based customization - - Compound components - -2. **Custom Hooks** - - Reusable logic extraction - - State management - - Side effects handling - -3. **Higher-Order Components (HOC)** - - `ProtectedRoute` for authentication - - Route guards - -4. **Render Props** - - Flexible component APIs - - Logic sharing - -5. **Container/Presentational Pattern** - - Separation of concerns - - Logic vs UI separation - -6. **API Client Pattern** - - Centralized API calls - - Consistent error handling - - Interceptor-based auth - -## Project Structure - -``` -yaltopia-ticket-admin/ -├── src/ -│ ├── app/ # App configuration -│ │ └── query-client.ts # React Query setup -│ ├── assets/ # Static assets -│ ├── components/ # Reusable components -│ │ ├── ui/ # Radix UI components -│ │ ├── ErrorBoundary.tsx # Error handling -│ │ └── ProtectedRoute.tsx # Auth guard -│ ├── layouts/ # Layout components -│ │ └── app-shell.tsx # Main layout -│ ├── lib/ # Utilities -│ │ ├── api-client.ts # Axios configuration -│ │ └── utils.ts # Helper functions -│ ├── pages/ # Page components -│ │ ├── admin/ # Admin pages -│ │ ├── login/ # Login page -│ │ └── ... -│ ├── App.tsx # Root component -│ ├── main.tsx # Entry point -│ └── index.css # Global styles -├── public/ # Public assets -├── dev-docs/ # Documentation -├── .env.example # Environment template -├── vite.config.ts # Vite configuration -├── tailwind.config.js # Tailwind configuration -├── tsconfig.json # TypeScript configuration -└── package.json # Dependencies - -``` - -## Performance Optimizations - -### Code Splitting -- **Manual Chunks** - Vendor code separation - - `react-vendor` - React core libraries - - `ui-vendor` - Radix UI components - - `chart-vendor` - Recharts library - - `query-vendor` - TanStack Query - -### Build Optimizations -- Tree shaking (unused code removal) -- Minification -- Compression -- Source map generation (disabled in production) -- Chunk size optimization (1000kb limit) - -### Runtime Optimizations -- React Query caching -- Lazy loading routes -- Image optimization -- Debounced search inputs -- Memoization where needed - -## Browser Support - -- Chrome (latest) -- Firefox (latest) -- Safari (latest) -- Edge (latest) - -**Minimum Versions:** -- Chrome 90+ -- Firefox 88+ -- Safari 14+ -- Edge 90+ - -## Development Environment - -### Requirements -- **Node.js**: 18+ (LTS recommended) -- **npm**: 9+ or **yarn**: 1.22+ -- **Git**: 2.0+ - -### Recommended IDE -- **VS Code** with extensions: - - ESLint - - Prettier - - Tailwind CSS IntelliSense - - TypeScript and JavaScript Language Features - - Auto Rename Tag - - Path Intellisense - -### Development Server -- **Port**: 5173 (configurable) -- **Hot Module Replacement**: Enabled -- **Host**: 0.0.0.0 (accessible from network) - -## Deployment Options - -### Static Hosting -- **Netlify** - Recommended -- **Vercel** - Recommended -- **AWS S3 + CloudFront** -- **Azure Static Web Apps** -- **GitHub Pages** - -### Container Deployment -- **Docker** - Nginx-based container -- **Kubernetes** - Scalable deployment -- **AWS ECS/Fargate** -- **Google Cloud Run** - -### CDN -- **CloudFlare** - Recommended for caching and security -- **AWS CloudFront** -- **Fastly** - -## Monitoring & Analytics (Optional) - -### Error Tracking -- **Sentry** - Error monitoring -- **LogRocket** - Session replay -- **Rollbar** - Error tracking - -### Analytics -- **Google Analytics 4** -- **Mixpanel** - Product analytics -- **Amplitude** - User behavior - -### Performance Monitoring -- **Lighthouse** - Performance audits -- **Web Vitals** - Core metrics -- **New Relic** - APM - -## Security Tools - -### Dependency Scanning -- `npm audit` - Vulnerability scanning -- **Snyk** - Continuous security monitoring -- **Dependabot** - Automated updates - -### Code Quality -- **SonarQube** - Code quality and security -- **CodeQL** - Security analysis - -## Testing (Not Yet Implemented) - -### Recommended Testing Stack -- **Vitest** - Unit testing (Vite-native) -- **React Testing Library** - Component testing -- **Playwright** - E2E testing -- **MSW** - API mocking - -## Comparison with Alternatives - -### Why React over Vue/Angular? -- Larger ecosystem -- Better TypeScript support -- More job opportunities -- Flexible architecture -- Strong community - -### Why Vite over Webpack/CRA? -- 10-100x faster HMR -- Faster cold starts -- Better developer experience -- Modern ES modules -- Smaller bundle sizes - -### Why Tailwind over CSS-in-JS? -- Better performance (no runtime) -- Smaller bundle size -- Easier to maintain -- Better IDE support -- Consistent design system - -### Why React Query over Redux? -- Less boilerplate -- Automatic caching -- Better for server state -- Simpler API -- Built-in loading/error states - -## Version History - -| Package | Current | Latest Stable | Notes | -|---------|---------|---------------|-------| -| React | 19.2.0 | 19.2.0 | ✅ Latest | -| TypeScript | 5.9.3 | 5.9.x | ✅ Latest | -| Vite | 7.2.4 | 7.x | ✅ Latest | -| React Router | 7.11.0 | 7.x | ✅ Latest | -| TanStack Query | 5.90.12 | 5.x | ✅ Latest | -| Tailwind CSS | 3.4.17 | 3.x | ✅ Latest | - -## Future Considerations - -### Potential Additions -- **React Hook Form** - Form management -- **Zod** - Schema validation -- **Zustand** - Lightweight state management -- **Framer Motion** - Advanced animations -- **i18next** - Internationalization -- **React Helmet** - SEO management - -### Potential Upgrades -- **React 19 Features** - Use new concurrent features -- **Vite 6** - When stable -- **TypeScript 5.10** - When released - -## Resources - -### Documentation -- [React Docs](https://react.dev) -- [TypeScript Docs](https://www.typescriptlang.org/docs) -- [Vite Docs](https://vitejs.dev) -- [Tailwind CSS Docs](https://tailwindcss.com/docs) -- [React Router Docs](https://reactrouter.com) -- [TanStack Query Docs](https://tanstack.com/query) -- [Radix UI Docs](https://www.radix-ui.com) - -### Learning Resources -- [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app) -- [Tailwind CSS Best Practices](https://tailwindcss.com/docs/reusing-styles) -- [React Query Tutorial](https://tanstack.com/query/latest/docs/framework/react/overview) - -## License -Proprietary - All rights reserved diff --git a/dev-docs/TROUBLESHOOTING.md b/dev-docs/TROUBLESHOOTING.md deleted file mode 100644 index 21c596e..0000000 --- a/dev-docs/TROUBLESHOOTING.md +++ /dev/null @@ -1,484 +0,0 @@ -# Troubleshooting Guide - -## Common Issues and Solutions - -### 1. ERR_CONNECTION_REFUSED - -**Error:** -``` -POST http://localhost:3000/api/v1/auth/login net::ERR_CONNECTION_REFUSED -``` - -**Cause:** Backend server is not running or running on a different port. - -**Solutions:** - -#### A. Start Your Backend Server -```bash -# Navigate to backend directory -cd path/to/backend - -# Start the server -npm run dev -# or -npm start -# or -node server.js -# or -python manage.py runserver # Django -# or -php artisan serve # Laravel -``` - -#### B. Check Backend Port -1. Find which port your backend is running on -2. Update `.env` file: -```env -# If backend is on port 3001 -VITE_API_URL=http://localhost:3001/api/v1 - -# If backend is on port 8000 -VITE_API_URL=http://localhost:8000/api/v1 - -# If backend is on port 5000 -VITE_API_URL=http://localhost:5000/api/v1 -``` - -3. Restart your frontend: -```bash -# Stop the dev server (Ctrl+C) -# Start again -npm run dev -``` - -#### C. Verify Backend is Running -```bash -# Test if backend is accessible -curl http://localhost:3000/api/v1/auth/login - -# Or open in browser -http://localhost:3000 -``` - -#### D. Check for Port Conflicts -```bash -# Windows - Check what's using port 3000 -netstat -ano | findstr :3000 - -# Kill process if needed (replace PID) -taskkill /PID /F -``` - ---- - -### 2. CORS Error - -**Error:** -``` -Access to XMLHttpRequest at 'http://localhost:3000/api/v1/auth/login' -from origin 'http://localhost:5173' has been blocked by CORS policy -``` - -**Cause:** Backend not configured to allow requests from frontend. - -**Solution:** Configure CORS on backend - -**Node.js/Express:** -```javascript -const cors = require('cors') - -app.use(cors({ - origin: 'http://localhost:5173', // Your frontend URL - credentials: true -})) -``` - -**Django:** -```python -# settings.py -CORS_ALLOWED_ORIGINS = [ - "http://localhost:5173", -] -CORS_ALLOW_CREDENTIALS = True -``` - -**Laravel:** -```php -// config/cors.php -'allowed_origins' => ['http://localhost:5173'], -'supports_credentials' => true, -``` - ---- - -### 3. 404 Not Found - -**Error:** -``` -POST http://localhost:3000/api/v1/auth/login 404 (Not Found) -``` - -**Cause:** Backend endpoint doesn't exist or path is wrong. - -**Solutions:** - -#### A. Verify Backend Route -Check if your backend has the login route: -```javascript -// Should have something like: -app.post('/api/v1/auth/login', loginController) -``` - -#### B. Check API Path -Your backend might use a different path: -```env -# If backend uses /api/auth/login -VITE_API_URL=http://localhost:3000/api - -# If backend uses /auth/login -VITE_API_URL=http://localhost:3000 - -# If backend uses /v1/auth/login -VITE_API_URL=http://localhost:3000/v1 -``` - -#### C. Test Backend Directly -```bash -# Test with curl -curl -X POST http://localhost:3000/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"test@example.com","password":"test123"}' -``` - ---- - -### 4. 401 Unauthorized - -**Error:** -``` -POST http://localhost:3000/api/v1/auth/login 401 (Unauthorized) -``` - -**Cause:** Invalid credentials or backend authentication issue. - -**Solutions:** - -#### A. Check Credentials -- Verify email/password are correct -- Check if user exists in database -- Verify user is active - -#### B. Check Backend Password Hashing -```javascript -// Backend should compare hashed passwords -const isValid = await bcrypt.compare(password, user.hashedPassword) -``` - -#### C. Check Database -```sql --- Verify user exists -SELECT * FROM users WHERE email = 'admin@example.com'; - --- Check if password is hashed -SELECT password FROM users WHERE email = 'admin@example.com'; -``` - ---- - -### 5. 403 Forbidden - -**Error:** -``` -POST http://localhost:3000/api/v1/auth/login 403 (Forbidden) -``` - -**Cause:** User doesn't have admin role or account is inactive. - -**Solutions:** - -#### A. Check User Role -```sql --- Update user role to ADMIN -UPDATE users SET role = 'ADMIN' WHERE email = 'admin@example.com'; -``` - -#### B. Check Active Status -```sql --- Activate user account -UPDATE users SET is_active = true WHERE email = 'admin@example.com'; -``` - -#### C. Frontend Validation -The frontend checks `user.role === 'ADMIN'`. Make sure backend returns correct role. - ---- - -### 6. Network Error (No Response) - -**Error:** -``` -Network Error -``` - -**Causes & Solutions:** - -#### A. Backend Crashed -Check backend console for errors and restart. - -#### B. Firewall Blocking -Temporarily disable firewall or add exception. - -#### C. Wrong Protocol -```env -# Use http for local development -VITE_API_URL=http://localhost:3000/api/v1 - -# NOT https -# VITE_API_URL=https://localhost:3000/api/v1 -``` - ---- - -### 7. Environment Variables Not Loading - -**Error:** -API calls go to wrong URL or undefined. - -**Solutions:** - -#### A. Create .env File -```bash -# Copy example file -cp .env.example .env - -# Edit with your values -VITE_API_URL=http://localhost:3000/api/v1 -``` - -#### B. Restart Dev Server -```bash -# Stop server (Ctrl+C) -# Start again -npm run dev -``` - -#### C. Check Variable Name -Must start with `VITE_`: -```env -# ✅ Correct -VITE_API_URL=http://localhost:3000/api/v1 - -# ❌ Wrong (won't work) -API_URL=http://localhost:3000/api/v1 -``` - -#### D. Access in Code -```typescript -// ✅ Correct -import.meta.env.VITE_API_URL - -// ❌ Wrong -process.env.VITE_API_URL -``` - ---- - -### 8. Token Not Persisting - -**Error:** -User logged out after page refresh. - -**Solutions:** - -#### A. Check localStorage -```javascript -// Open browser console -localStorage.getItem('access_token') -localStorage.getItem('user') -``` - -#### B. Check Cookie Settings -If using httpOnly cookies, check browser DevTools > Application > Cookies. - -#### C. Backend Must Return Token -```json -{ - "access_token": "...", - "user": { ... } -} -``` - ---- - -### 9. Infinite Redirect Loop - -**Error:** -Page keeps redirecting between login and dashboard. - -**Solutions:** - -#### A. Check ProtectedRoute Logic -```typescript -// Should check for token -const token = localStorage.getItem('access_token') -if (!token) { - return -} -``` - -#### B. Clear localStorage -```javascript -// Browser console -localStorage.clear() -// Then try logging in again -``` - ---- - -### 10. Tests Hanging - -**Error:** -Tests run forever without completing. - -**Solutions:** - -#### A. Add Timeout -```typescript -// In test file -import { vi } from 'vitest' - -vi.setConfig({ testTimeout: 10000 }) -``` - -#### B. Mock Timers -```typescript -vi.useFakeTimers() -// ... test code -vi.useRealTimers() -``` - -#### C. Check for Unresolved Promises -Make sure all async operations complete. - ---- - -## Debugging Tips - -### 1. Check Browser Console -Press F12 and look for errors in Console tab. - -### 2. Check Network Tab -1. Press F12 -2. Go to Network tab -3. Try logging in -4. Click on the failed request -5. Check: - - Request URL - - Request Headers - - Request Payload - - Response - -### 3. Check Backend Logs -Look at your backend console for error messages. - -### 4. Test Backend Independently -Use curl or Postman to test backend without frontend. - -### 5. Verify Environment Variables -```bash -# Check if .env file exists -ls -la .env - -# Check contents -cat .env -``` - -### 6. Clear Browser Cache -Sometimes old cached files cause issues: -1. Press Ctrl+Shift+Delete -2. Clear cache and cookies -3. Restart browser - -### 7. Check Node Version -```bash -node --version -# Should be 18.x or 20.x -``` - ---- - -## Quick Checklist - -Before asking for help, verify: - -- [ ] Backend server is running -- [ ] Backend is on correct port -- [ ] `.env` file exists with correct API URL -- [ ] Frontend dev server restarted after .env changes -- [ ] CORS configured on backend -- [ ] Login endpoint exists on backend -- [ ] Test user exists in database -- [ ] User has ADMIN role -- [ ] User account is active -- [ ] Browser console shows no errors -- [ ] Network tab shows request details - ---- - -## Getting Help - -If still stuck: - -1. **Check Documentation** - - [Authentication Setup](./AUTHENTICATION.md) - - [Login API Documentation](./LOGIN_API_DOCUMENTATION.md) - - [API Standards](./API_STANDARDS.md) - -2. **Gather Information** - - Error message - - Browser console logs - - Network tab details - - Backend logs - - Environment variables - -3. **Test Systematically** - - Test backend with curl - - Test with Postman - - Check database directly - - Verify each step - ---- - -## Common Development Setup - -### Typical Setup -``` -Frontend: http://localhost:5173 (Vite) -Backend: http://localhost:3000 (Node.js) -Database: localhost:5432 (PostgreSQL) -``` - -### .env Configuration -```env -VITE_API_URL=http://localhost:3000/api/v1 -VITE_ENV=development -``` - -### Backend CORS -```javascript -app.use(cors({ - origin: 'http://localhost:5173', - credentials: true -})) -``` - -### Test Login -```bash -curl -X POST http://localhost:3000/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{"email":"admin@example.com","password":"admin123"}' -``` - ---- - -**Last Updated:** 2024 diff --git a/src/layouts/app-shell.tsx b/src/layouts/app-shell.tsx index e03f7d5..b24e212 100644 --- a/src/layouts/app-shell.tsx +++ b/src/layouts/app-shell.tsx @@ -20,7 +20,7 @@ import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Avatar, AvatarFallback } from "@/components/ui/avatar" import { cn } from "@/lib/utils" -import { adminApiHelpers } from "@/lib/api-client" +import { authService } from "@/services" interface User { email: string @@ -70,8 +70,8 @@ export function AppShell() { return item?.label || "Admin Panel" } - const handleLogout = () => { - adminApiHelpers.logout() + const handleLogout = async () => { + await authService.logout() navigate('/login', { replace: true }) } diff --git a/src/lib/api-client.ts b/src/lib/api-client.ts deleted file mode 100644 index dc27917..0000000 --- a/src/lib/api-client.ts +++ /dev/null @@ -1,335 +0,0 @@ -import axios, { type AxiosInstance, type AxiosError } from 'axios'; - -const API_BASE_URL = import.meta.env.VITE_BACKEND_API_URL || 'http://localhost:3000/api/v1'; - -// Create separate axios instance for public endpoints (no auth required) -const publicApi: AxiosInstance = axios.create({ - baseURL: API_BASE_URL, - headers: { - 'Content-Type': 'application/json', - }, - withCredentials: true, // Important: Send cookies with requests -}); - -// Create axios instance for authenticated endpoints -const adminApi: AxiosInstance = axios.create({ - baseURL: API_BASE_URL, - headers: { - 'Content-Type': 'application/json', - }, - withCredentials: true, // Important: Send cookies with requests -}); - -// Add token interceptor for localStorage fallback (if not using cookies) -adminApi.interceptors.request.use( - (config) => { - // Only add Authorization header if token exists in localStorage - // (This is fallback - cookies are preferred) - const token = localStorage.getItem('access_token'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - return config; - }, - (error) => { - return Promise.reject(error); - } -); - -// Add response interceptor for error handling -adminApi.interceptors.response.use( - (response) => response, - async (error: AxiosError<{ message?: string }>) => { - const originalRequest = error.config as any; - - // Handle 401 Unauthorized - if (error.response?.status === 401) { - // Don't redirect if already on login page - if (window.location.pathname.includes('/login')) { - return Promise.reject(error); - } - - // Try to refresh token if not already retrying - if (!originalRequest._retry) { - originalRequest._retry = true; - - try { - // Attempt token refresh - await adminApiHelpers.refreshToken(); - // Retry original request - return adminApi(originalRequest); - } catch (refreshError) { - // Refresh failed, logout user - adminApiHelpers.logout(); - window.location.href = '/login'; - return Promise.reject(refreshError); - } - } - - // If retry failed, logout - adminApiHelpers.logout(); - window.location.href = '/login'; - } else if (error.response?.status === 403) { - // Show access denied - const message = error.response?.data?.message || 'You do not have permission to access this resource'; - if (typeof window !== 'undefined') { - import('sonner').then(({ toast }) => { - toast.error(message); - }); - } - } else if (error.response?.status === 404) { - const message = error.response?.data?.message || 'Resource not found'; - if (typeof window !== 'undefined') { - import('sonner').then(({ toast }) => { - toast.error(message); - }); - } - } else if (error.response?.status === 500) { - const message = error.response?.data?.message || 'Server error occurred. Please try again later.'; - if (typeof window !== 'undefined') { - import('sonner').then(({ toast }) => { - toast.error(message); - }); - } - } else if (!error.response) { - // Network error - if (typeof window !== 'undefined') { - import('sonner').then(({ toast }) => { - toast.error('Network error. Please check your connection.'); - }); - } - } - return Promise.reject(error); - } -); - -// API helper functions -export const adminApiHelpers = { - // Auth - uses publicApi (no token required) - login: (data: { email: string; password: string }) => - publicApi.post('/auth/login', data), - - logout: async () => { - try { - // Call backend logout to clear httpOnly cookies - await adminApi.post('/auth/logout'); - } catch (error) { - console.error('Logout error:', error); - } finally { - // Always clear localStorage - localStorage.removeItem('access_token'); - localStorage.removeItem('user'); - } - }, - - refreshToken: () => adminApi.post('/auth/refresh'), - - getCurrentUser: () => adminApi.get('/auth/me'), - - // Users - getUsers: (params?: { - page?: number; - limit?: number; - role?: string; - isActive?: boolean; - search?: string; - }) => adminApi.get('/admin/users', { params }), - - getUser: (id: string) => adminApi.get(`/admin/users/${id}`), - - getUserActivity: (id: string, days: number = 30) => - adminApi.get(`/admin/users/${id}/activity`, { params: { days } }), - - updateUser: (id: string, data: { - role?: string; - isActive?: boolean; - firstName?: string; - lastName?: string; - }) => adminApi.put(`/admin/users/${id}`, data), - - deleteUser: (id: string, hard: boolean = false) => - adminApi.delete(`/admin/users/${id}?hard=${hard}`), - - resetPassword: (id: string) => - adminApi.post(`/admin/users/${id}/reset-password`), - - exportUsers: (format: string = 'csv') => - adminApi.post('/admin/users/export', null, { params: { format } }), - - importUsers: (file: File) => { - const formData = new FormData(); - formData.append('file', file); - return adminApi.post('/admin/users/import', formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); - }, - - // Logs - getLogs: (params?: { - page?: number; - limit?: number; - level?: string; - type?: string; - userId?: string; - startDate?: string; - endDate?: string; - search?: string; - minDuration?: number; - }) => adminApi.get('/admin/logs', { params }), - - getErrorLogs: (params?: { - page?: number; - limit?: number; - userId?: string; - startDate?: string; - endDate?: string; - }) => adminApi.get('/admin/logs/errors', { params }), - - getAccessLogs: (params?: { - page?: number; - limit?: number; - userId?: string; - startDate?: string; - endDate?: string; - }) => adminApi.get('/admin/logs/access', { params }), - - getLogById: (id: string) => adminApi.get(`/admin/logs/${id}`), - - getLogStats: (startDate?: string, endDate?: string) => - adminApi.get('/admin/logs/stats/summary', { params: { startDate, endDate } }), - - exportLogs: (params: { - format?: string; - level?: string; - startDate?: string; - endDate?: string; - }) => adminApi.post('/admin/logs/export', null, { params }), - - cleanupLogs: (days: number = 30) => - adminApi.post('/admin/logs/cleanup', null, { params: { days } }), - - // Analytics - getOverview: () => adminApi.get('/admin/analytics/overview'), - - getUserGrowth: (days: number = 30) => - adminApi.get('/admin/analytics/users/growth', { params: { days } }), - - getRevenue: (period: string = '30days') => - adminApi.get('/admin/analytics/revenue', { params: { period } }), - - getStorageAnalytics: () => adminApi.get('/admin/analytics/storage'), - - getApiUsage: (days: number = 7) => - adminApi.get('/admin/analytics/api-usage', { params: { days } }), - - getErrorRate: (days: number = 7) => - adminApi.get('/admin/analytics/error-rate', { params: { days } }), - - // System - getHealth: () => adminApi.get('/admin/system/health'), - - getSystemInfo: () => adminApi.get('/admin/system/info'), - - getSettings: (category?: string) => - adminApi.get('/admin/system/settings', { params: { category } }), - - getSetting: (key: string) => adminApi.get(`/admin/system/settings/${key}`), - - createSetting: (data: { - key: string; - value: string; - category: string; - description?: string; - isPublic?: boolean; - }) => adminApi.post('/admin/system/settings', data), - - updateSetting: (key: string, data: { - value: string; - description?: string; - isPublic?: boolean; - }) => adminApi.put(`/admin/system/settings/${key}`, data), - - deleteSetting: (key: string) => adminApi.delete(`/admin/system/settings/${key}`), - - // Maintenance - getMaintenanceStatus: () => adminApi.get('/admin/maintenance'), - - enableMaintenance: (message?: string) => - adminApi.post('/admin/maintenance/enable', { message }), - - disableMaintenance: () => adminApi.post('/admin/maintenance/disable'), - - // Announcements - getAnnouncements: (activeOnly: boolean = false) => - adminApi.get('/admin/announcements', { params: { activeOnly } }), - - createAnnouncement: (data: { - title: string; - message: string; - type?: string; - priority?: number; - targetAudience?: string; - startsAt?: string; - endsAt?: string; - }) => adminApi.post('/admin/announcements', data), - - updateAnnouncement: (id: string, data: { - title?: string; - message?: string; - type?: string; - priority?: number; - targetAudience?: string; - startsAt?: string; - endsAt?: string; - }) => adminApi.put(`/admin/announcements/${id}`, data), - - toggleAnnouncement: (id: string) => - adminApi.patch(`/admin/announcements/${id}/toggle`), - - deleteAnnouncement: (id: string) => - adminApi.delete(`/admin/announcements/${id}`), - - // Audit - getAuditLogs: (params?: { - page?: number; - limit?: number; - userId?: string; - action?: string; - resourceType?: string; - resourceId?: string; - startDate?: string; - endDate?: string; - }) => adminApi.get('/admin/audit/logs', { params }), - - getUserAuditActivity: (userId: string, days: number = 30) => - adminApi.get(`/admin/audit/users/${userId}`, { params: { days } }), - - getResourceHistory: (type: string, id: string) => - adminApi.get(`/admin/audit/resource/${type}/${id}`), - - getAuditStats: (startDate?: string, endDate?: string) => - adminApi.get('/admin/audit/stats', { params: { startDate, endDate } }), - - // Security - getFailedLogins: (params?: { - page?: number; - limit?: number; - email?: string; - ipAddress?: string; - }) => adminApi.get('/admin/security/failed-logins', { params }), - - getSuspiciousActivity: () => adminApi.get('/admin/security/suspicious-activity'), - - getAllApiKeys: () => adminApi.get('/admin/security/api-keys'), - - revokeApiKey: (id: string) => - adminApi.patch(`/admin/security/api-keys/${id}/revoke`), - - getRateLimitViolations: (days: number = 7) => - adminApi.get('/admin/security/rate-limits', { params: { days } }), - - getActiveSessions: () => adminApi.get('/admin/security/sessions'), -}; - -export default adminApi; - diff --git a/src/lib/error-tracker.ts b/src/lib/error-tracker.ts index a4db758..91167d9 100644 --- a/src/lib/error-tracker.ts +++ b/src/lib/error-tracker.ts @@ -1,5 +1,5 @@ import { Sentry } from './sentry' -import adminApi from './api-client' +import apiClient from '@/services/api/client' interface ErrorLog { message: string @@ -105,7 +105,7 @@ class ErrorTracker { try { // Send to your backend error logging endpoint - await adminApi.post('/errors/log', errorLog) + await apiClient.post('/errors/log', errorLog) this.queue.shift() // Remove from queue on success } catch (error) { console.error('Failed to log error to backend:', error) diff --git a/src/pages/admin/analytics/api.tsx b/src/pages/admin/analytics/api.tsx index 6aa9d6f..8389620 100644 --- a/src/pages/admin/analytics/api.tsx +++ b/src/pages/admin/analytics/api.tsx @@ -8,23 +8,17 @@ import { TableHeader, TableRow, } from "@/components/ui/table" -import { adminApiHelpers } from "@/lib/api-client" +import { analyticsService } from "@/services" export default function AnalyticsApiPage() { const { data: apiUsage, isLoading } = useQuery({ queryKey: ['admin', 'analytics', 'api-usage'], - queryFn: async () => { - const response = await adminApiHelpers.getApiUsage(7) - return response.data - }, + queryFn: () => analyticsService.getApiUsage(7), }) const { data: errorRate, isLoading: errorRateLoading } = useQuery({ queryKey: ['admin', 'analytics', 'error-rate'], - queryFn: async () => { - const response = await adminApiHelpers.getErrorRate(7) - return response.data - }, + queryFn: () => analyticsService.getErrorRate(7), }) return ( diff --git a/src/pages/admin/analytics/revenue.tsx b/src/pages/admin/analytics/revenue.tsx index 2fa11b1..44c1794 100644 --- a/src/pages/admin/analytics/revenue.tsx +++ b/src/pages/admin/analytics/revenue.tsx @@ -1,15 +1,12 @@ import { useQuery } from "@tanstack/react-query" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts" -import { adminApiHelpers } from "@/lib/api-client" +import { analyticsService } from "@/services" export default function AnalyticsRevenuePage() { const { data: revenue, isLoading } = useQuery({ queryKey: ['admin', 'analytics', 'revenue'], - queryFn: async () => { - const response = await adminApiHelpers.getRevenue('90days') - return response.data - }, + queryFn: () => analyticsService.getRevenue('90days'), }) return ( diff --git a/src/pages/admin/analytics/storage.tsx b/src/pages/admin/analytics/storage.tsx index 81504b5..92af5f6 100644 --- a/src/pages/admin/analytics/storage.tsx +++ b/src/pages/admin/analytics/storage.tsx @@ -1,17 +1,14 @@ import { useQuery } from "@tanstack/react-query" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend } from "recharts" -import { adminApiHelpers } from "@/lib/api-client" +import { analyticsService } from "@/services" const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8'] export default function AnalyticsStoragePage() { const { data: storage, isLoading } = useQuery({ queryKey: ['admin', 'analytics', 'storage'], - queryFn: async () => { - const response = await adminApiHelpers.getStorageAnalytics() - return response.data - }, + queryFn: () => analyticsService.getStorageAnalytics(), }) const formatBytes = (bytes: number) => { diff --git a/src/pages/admin/analytics/users.tsx b/src/pages/admin/analytics/users.tsx index 9d4377a..b4e16d6 100644 --- a/src/pages/admin/analytics/users.tsx +++ b/src/pages/admin/analytics/users.tsx @@ -1,15 +1,12 @@ import { useQuery } from "@tanstack/react-query" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts" -import { adminApiHelpers } from "@/lib/api-client" +import { analyticsService } from "@/services" export default function AnalyticsUsersPage() { const { data: userGrowth, isLoading } = useQuery({ queryKey: ['admin', 'analytics', 'users', 'growth'], - queryFn: async () => { - const response = await adminApiHelpers.getUserGrowth(90) - return response.data - }, + queryFn: () => analyticsService.getUserGrowth(90), }) return ( diff --git a/src/pages/admin/announcements/index.tsx b/src/pages/admin/announcements/index.tsx index b95253c..a7a6a1c 100644 --- a/src/pages/admin/announcements/index.tsx +++ b/src/pages/admin/announcements/index.tsx @@ -20,7 +20,7 @@ import { DialogTitle, } from "@/components/ui/dialog" import { Plus, Edit, Trash2 } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { announcementService } from "@/services" import { toast } from "sonner" import { format } from "date-fns" @@ -31,16 +31,11 @@ export default function AnnouncementsPage() { const { data: announcements, isLoading } = useQuery({ queryKey: ['admin', 'announcements'], - queryFn: async () => { - const response = await adminApiHelpers.getAnnouncements(false) - return response.data - }, + queryFn: () => announcementService.getAnnouncements(false), }) const deleteMutation = useMutation({ - mutationFn: async (id: string) => { - await adminApiHelpers.deleteAnnouncement(id) - }, + mutationFn: (id: string) => announcementService.deleteAnnouncement(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'announcements'] }) toast.success("Announcement deleted successfully") diff --git a/src/pages/admin/audit/index.tsx b/src/pages/admin/audit/index.tsx index 0d3489b..c472176 100644 --- a/src/pages/admin/audit/index.tsx +++ b/src/pages/admin/audit/index.tsx @@ -13,7 +13,7 @@ import { TableRow, } from "@/components/ui/table" import { Search, Eye } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { auditService } from "@/services" import { format } from "date-fns" export default function AuditPage() { @@ -26,8 +26,7 @@ export default function AuditPage() { queryFn: async () => { const params: any = { page, limit } if (search) params.search = search - const response = await adminApiHelpers.getAuditLogs(params) - return response.data + return await auditService.getAuditLogs(params) }, }) diff --git a/src/pages/admin/dashboard/index.tsx b/src/pages/admin/dashboard/index.tsx index 0f31fcd..a6746d3 100644 --- a/src/pages/admin/dashboard/index.tsx +++ b/src/pages/admin/dashboard/index.tsx @@ -2,49 +2,34 @@ import { useQuery } from "@tanstack/react-query" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Download, Users, FileText, DollarSign, HardDrive, AlertCircle } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { analyticsService, systemService } from "@/services" import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts" import { toast } from "sonner" export default function DashboardPage() { const { data: overview, isLoading: overviewLoading } = useQuery({ queryKey: ['admin', 'analytics', 'overview'], - queryFn: async () => { - const response = await adminApiHelpers.getOverview() - return response.data - }, + queryFn: () => analyticsService.getOverview(), }) const { data: userGrowth, isLoading: growthLoading } = useQuery({ queryKey: ['admin', 'analytics', 'users', 'growth'], - queryFn: async () => { - const response = await adminApiHelpers.getUserGrowth(30) - return response.data - }, + queryFn: () => analyticsService.getUserGrowth(30), }) const { data: revenue, isLoading: revenueLoading } = useQuery({ queryKey: ['admin', 'analytics', 'revenue'], - queryFn: async () => { - const response = await adminApiHelpers.getRevenue('30days') - return response.data - }, + queryFn: () => analyticsService.getRevenue('30days'), }) const { data: health, isLoading: healthLoading } = useQuery({ queryKey: ['admin', 'system', 'health'], - queryFn: async () => { - const response = await adminApiHelpers.getHealth() - return response.data - }, + queryFn: () => systemService.getHealth(), }) const { data: errorRate, isLoading: errorRateLoading } = useQuery({ queryKey: ['admin', 'analytics', 'error-rate'], - queryFn: async () => { - const response = await adminApiHelpers.getErrorRate(7) - return response.data - }, + queryFn: () => analyticsService.getErrorRate(7), }) const handleExport = () => { diff --git a/src/pages/admin/health/index.tsx b/src/pages/admin/health/index.tsx index 36df5c2..478596d 100644 --- a/src/pages/admin/health/index.tsx +++ b/src/pages/admin/health/index.tsx @@ -2,27 +2,21 @@ import { useQuery } from "@tanstack/react-query" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { AlertCircle, CheckCircle, XCircle, Users } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { systemService } from "@/services" export default function HealthPage() { const { data: health, isLoading: healthLoading } = useQuery({ queryKey: ['admin', 'system', 'health'], - queryFn: async () => { - const response = await adminApiHelpers.getHealth() - return response.data - }, + queryFn: () => systemService.getHealth(), refetchInterval: 30000, // Refetch every 30 seconds }) const { data: systemInfo, isLoading: infoLoading } = useQuery({ queryKey: ['admin', 'system', 'info'], - queryFn: async () => { - const response = await adminApiHelpers.getSystemInfo() - return response.data - }, + queryFn: () => systemService.getSystemInfo(), }) - const getStatusIcon = (status: string) => { + const getStatusIcon = (status?: string) => { switch (status?.toLowerCase()) { case 'healthy': case 'connected': @@ -142,19 +136,19 @@ export default function HealthPage() {

Platform

-

{systemInfo.platform}

+

{systemInfo.platform || 'N/A'}

Architecture

-

{systemInfo.architecture}

+

{systemInfo.architecture || 'N/A'}

Uptime

-

{formatUptime(systemInfo.uptime)}

+

{formatUptime(systemInfo.uptime || 0)}

Environment

-

{systemInfo.env}

+

{systemInfo.env || systemInfo.environment}

Memory Usage

diff --git a/src/pages/admin/maintenance/index.tsx b/src/pages/admin/maintenance/index.tsx index e830d05..4eda933 100644 --- a/src/pages/admin/maintenance/index.tsx +++ b/src/pages/admin/maintenance/index.tsx @@ -4,7 +4,7 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Switch } from "@/components/ui/switch" import { Badge } from "@/components/ui/badge" -import { adminApiHelpers } from "@/lib/api-client" +import { systemService } from "@/services" import { toast } from "sonner" import { useState } from "react" @@ -14,16 +14,11 @@ export default function MaintenancePage() { const { data: status, isLoading } = useQuery({ queryKey: ['admin', 'maintenance'], - queryFn: async () => { - const response = await adminApiHelpers.getMaintenanceStatus() - return response.data - }, + queryFn: () => systemService.getMaintenanceStatus(), }) const enableMutation = useMutation({ - mutationFn: async (msg?: string) => { - await adminApiHelpers.enableMaintenance(msg) - }, + mutationFn: (msg?: string) => systemService.enableMaintenance(msg), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'maintenance'] }) toast.success("Maintenance mode enabled") @@ -35,9 +30,7 @@ export default function MaintenancePage() { }) const disableMutation = useMutation({ - mutationFn: async () => { - await adminApiHelpers.disableMaintenance() - }, + mutationFn: () => systemService.disableMaintenance(), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'maintenance'] }) toast.success("Maintenance mode disabled") diff --git a/src/pages/admin/security/api-keys.tsx b/src/pages/admin/security/api-keys.tsx index 541120a..a2a7bd2 100644 --- a/src/pages/admin/security/api-keys.tsx +++ b/src/pages/admin/security/api-keys.tsx @@ -11,7 +11,7 @@ import { TableRow, } from "@/components/ui/table" import { Ban } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { securityService } from "@/services" import { toast } from "sonner" import { format } from "date-fns" @@ -20,16 +20,11 @@ export default function ApiKeysPage() { const { data: apiKeys, isLoading } = useQuery({ queryKey: ['admin', 'security', 'api-keys'], - queryFn: async () => { - const response = await adminApiHelpers.getAllApiKeys() - return response.data - }, + queryFn: () => securityService.getAllApiKeys(), }) const revokeMutation = useMutation({ - mutationFn: async (id: string) => { - await adminApiHelpers.revokeApiKey(id) - }, + mutationFn: (id: string) => securityService.revokeApiKey(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'security', 'api-keys'] }) toast.success("API key revoked successfully") diff --git a/src/pages/admin/security/failed-logins.tsx b/src/pages/admin/security/failed-logins.tsx index aff3455..6c77635 100644 --- a/src/pages/admin/security/failed-logins.tsx +++ b/src/pages/admin/security/failed-logins.tsx @@ -13,7 +13,7 @@ import { TableRow, } from "@/components/ui/table" import { Search, Ban } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { securityService } from "@/services" import { format } from "date-fns" export default function FailedLoginsPage() { @@ -26,8 +26,7 @@ export default function FailedLoginsPage() { queryFn: async () => { const params: any = { page, limit } if (search) params.email = search - const response = await adminApiHelpers.getFailedLogins(params) - return response.data + return await securityService.getFailedLogins(params) }, }) diff --git a/src/pages/admin/security/rate-limits.tsx b/src/pages/admin/security/rate-limits.tsx index ab25f52..ed928ba 100644 --- a/src/pages/admin/security/rate-limits.tsx +++ b/src/pages/admin/security/rate-limits.tsx @@ -8,15 +8,12 @@ import { TableHeader, TableRow, } from "@/components/ui/table" -import { adminApiHelpers } from "@/lib/api-client" +import { securityService } from "@/services" export default function RateLimitsPage() { const { data: violations, isLoading } = useQuery({ queryKey: ['admin', 'security', 'rate-limits'], - queryFn: async () => { - const response = await adminApiHelpers.getRateLimitViolations(7) - return response.data - }, + queryFn: () => securityService.getRateLimitViolations(7), }) return ( diff --git a/src/pages/admin/security/sessions.tsx b/src/pages/admin/security/sessions.tsx index 891b9a0..6886171 100644 --- a/src/pages/admin/security/sessions.tsx +++ b/src/pages/admin/security/sessions.tsx @@ -10,16 +10,13 @@ import { TableRow, } from "@/components/ui/table" import { LogOut } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { securityService } from "@/services" import { format } from "date-fns" export default function SessionsPage() { const { data: sessions, isLoading } = useQuery({ queryKey: ['admin', 'security', 'sessions'], - queryFn: async () => { - const response = await adminApiHelpers.getActiveSessions() - return response.data - }, + queryFn: () => securityService.getActiveSessions(), }) return ( diff --git a/src/pages/admin/security/suspicious.tsx b/src/pages/admin/security/suspicious.tsx index 41c3681..3a72fda 100644 --- a/src/pages/admin/security/suspicious.tsx +++ b/src/pages/admin/security/suspicious.tsx @@ -2,15 +2,12 @@ import { useQuery } from "@tanstack/react-query" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Shield, Ban } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { securityService } from "@/services" export default function SuspiciousActivityPage() { const { data: suspicious, isLoading } = useQuery({ queryKey: ['admin', 'security', 'suspicious'], - queryFn: async () => { - const response = await adminApiHelpers.getSuspiciousActivity() - return response.data - }, + queryFn: () => securityService.getSuspiciousActivity(), }) return ( @@ -28,9 +25,9 @@ export default function SuspiciousActivityPage() { {isLoading ? (
Loading...
- ) : suspicious?.suspiciousIPs?.length > 0 ? ( + ) : (suspicious?.suspiciousIPs?.length ?? 0) > 0 ? (
- {suspicious.suspiciousIPs.map((ip: any, index: number) => ( + {suspicious?.suspiciousIPs?.map((ip: any, index: number) => (

{ip.ipAddress}

@@ -61,9 +58,9 @@ export default function SuspiciousActivityPage() { {isLoading ? (
Loading...
- ) : suspicious?.suspiciousEmails?.length > 0 ? ( + ) : (suspicious?.suspiciousEmails?.length ?? 0) > 0 ? (
- {suspicious.suspiciousEmails.map((email: any, index: number) => ( + {suspicious?.suspiciousEmails?.map((email: any, index: number) => (

{email.email}

diff --git a/src/pages/admin/settings/index.tsx b/src/pages/admin/settings/index.tsx index 8a2a7c9..784f002 100644 --- a/src/pages/admin/settings/index.tsx +++ b/src/pages/admin/settings/index.tsx @@ -15,7 +15,7 @@ import { DialogTitle, } from "@/components/ui/dialog" import { Plus } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { settingsService } from "@/services" import { toast } from "sonner" export default function SettingsPage() { @@ -31,16 +31,12 @@ export default function SettingsPage() { const { data: settings, isLoading } = useQuery({ queryKey: ['admin', 'settings', selectedCategory], - queryFn: async () => { - const response = await adminApiHelpers.getSettings(selectedCategory) - return response.data - }, + queryFn: () => settingsService.getSettings(selectedCategory), }) const updateSettingMutation = useMutation({ - mutationFn: async ({ key, value }: { key: string; value: string }) => { - await adminApiHelpers.updateSetting(key, { value }) - }, + mutationFn: ({ key, value }: { key: string; value: string }) => + settingsService.updateSetting(key, { value }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'settings'] }) toast.success("Setting updated successfully") @@ -51,15 +47,13 @@ export default function SettingsPage() { }) const createSettingMutation = useMutation({ - mutationFn: async (data: { + mutationFn: (data: { key: string value: string category: string description?: string isPublic?: boolean - }) => { - await adminApiHelpers.createSetting(data) - }, + }) => settingsService.createSetting(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'settings'] }) toast.success("Setting created successfully") diff --git a/src/pages/admin/users/[id]/activity.tsx b/src/pages/admin/users/[id]/activity.tsx index 29f17b2..36e657f 100644 --- a/src/pages/admin/users/[id]/activity.tsx +++ b/src/pages/admin/users/[id]/activity.tsx @@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { ArrowLeft } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { userService } from "@/services" import { format } from "date-fns" export default function UserActivityPage() { @@ -12,10 +12,7 @@ export default function UserActivityPage() { const { data: activity, isLoading } = useQuery({ queryKey: ['admin', 'users', id, 'activity'], - queryFn: async () => { - const response = await adminApiHelpers.getUserActivity(id!, 30) - return response.data - }, + queryFn: () => userService.getUserActivity(id!, 30), enabled: !!id, }) diff --git a/src/pages/admin/users/[id]/index.tsx b/src/pages/admin/users/[id]/index.tsx index 85ea85f..698798c 100644 --- a/src/pages/admin/users/[id]/index.tsx +++ b/src/pages/admin/users/[id]/index.tsx @@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { ArrowLeft, Edit, Key } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { userService } from "@/services" import { format } from "date-fns" export default function UserDetailsPage() { @@ -14,10 +14,7 @@ export default function UserDetailsPage() { const { data: user, isLoading } = useQuery({ queryKey: ['admin', 'users', id], - queryFn: async () => { - const response = await adminApiHelpers.getUser(id!) - return response.data - }, + queryFn: () => userService.getUser(id!), enabled: !!id, }) @@ -90,7 +87,9 @@ export default function UserDetailsPage() {

Updated At

-

{format(new Date(user.updatedAt), 'PPpp')}

+

+ {user.updatedAt ? format(new Date(user.updatedAt), 'PPpp') : 'N/A'} +

diff --git a/src/pages/admin/users/index.tsx b/src/pages/admin/users/index.tsx index 19bd804..a9b6fd0 100644 --- a/src/pages/admin/users/index.tsx +++ b/src/pages/admin/users/index.tsx @@ -30,7 +30,7 @@ import { DialogTitle, } from "@/components/ui/dialog" import { Search, Download, Eye, UserPlus, Trash2, Key, Upload } from "lucide-react" -import { adminApiHelpers } from "@/lib/api-client" +import { userService } from "@/services" import { toast } from "sonner" import { format } from "date-fns" @@ -55,15 +55,13 @@ export default function UsersPage() { if (search) params.search = search if (roleFilter !== 'all') params.role = roleFilter if (statusFilter !== 'all') params.isActive = statusFilter === 'active' - const response = await adminApiHelpers.getUsers(params) - return response.data + return await userService.getUsers(params) }, }) const deleteUserMutation = useMutation({ - mutationFn: async ({ id, hard }: { id: string; hard: boolean }) => { - await adminApiHelpers.deleteUser(id, hard) - }, + mutationFn: ({ id, hard }: { id: string; hard: boolean }) => + userService.deleteUser(id, hard), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'users'] }) toast.success("User deleted successfully") @@ -75,10 +73,7 @@ export default function UsersPage() { }) const resetPasswordMutation = useMutation({ - mutationFn: async (id: string) => { - const response = await adminApiHelpers.resetPassword(id) - return response.data - }, + mutationFn: (id: string) => userService.resetPassword(id), onSuccess: (data) => { toast.success(`Password reset. Temporary password: ${data.temporaryPassword}`) setResetPasswordDialogOpen(false) @@ -89,18 +84,12 @@ export default function UsersPage() { }) const importUsersMutation = useMutation({ - mutationFn: async (file: File) => { - const response = await adminApiHelpers.importUsers(file) - return response.data - }, + mutationFn: (file: File) => userService.importUsers(file), onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['admin', 'users'] }) - toast.success(`Imported ${data.success} users. ${data.failed} failed.`) + toast.success(`Imported ${data.imported} users. ${data.failed} failed.`) setImportDialogOpen(false) setImportFile(null) - if (data.errors && data.errors.length > 0) { - console.error('Import errors:', data.errors) - } }, onError: (error: any) => { toast.error(error.response?.data?.message || "Failed to import users") @@ -109,8 +98,7 @@ export default function UsersPage() { const handleExport = async () => { try { - const response = await adminApiHelpers.exportUsers('csv') - const blob = new Blob([response.data], { type: 'text/csv' }) + const blob = await userService.exportUsers('csv') const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index e307905..8b72a58 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -1,35 +1,175 @@ +import { useQuery } from "@tanstack/react-query" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" -import { Download } from "lucide-react" +import { Download, FileText, DollarSign, CreditCard, TrendingUp } from "lucide-react" +import { dashboardService } from "@/services" +import { toast } from "sonner" export default function DashboardPage() { + const { data: profile } = useQuery({ + queryKey: ['user', 'profile'], + queryFn: () => dashboardService.getUserProfile(), + }) + + const { data: stats, isLoading } = useQuery({ + queryKey: ['user', 'stats'], + queryFn: () => dashboardService.getUserStats(), + }) + + const handleExport = () => { + toast.success("Exporting your data...") + } + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }).format(amount) + } + + const getGreeting = () => { + const hour = new Date().getHours() + if (hour < 12) return 'Good Morning' + if (hour < 18) return 'Good Afternoon' + return 'Good Evening' + } + + const userName = profile ? `${profile.firstName} ${profile.lastName}` : 'User' + return (
-

Good Morning, Admin

+

{getGreeting()}, {userName}

- 01 Sep - 15 Sep 2024 + {new Date().toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + })}
-
-
+
- - Welcome to Dashboard + + Total Invoices + -

- This is your main dashboard page. -

+ {isLoading ? ( +
...
+ ) : ( + <> +
{stats?.totalInvoices || 0}
+

+ {stats?.pendingInvoices || 0} pending +

+ + )} +
+
+ + + + Total Transactions + + + + {isLoading ? ( +
...
+ ) : ( + <> +
{stats?.totalTransactions || 0}
+

+ All time transactions +

+ + )} +
+
+ + + + Total Revenue + + + + {isLoading ? ( +
...
+ ) : ( + <> +
+ {formatCurrency(stats?.totalRevenue || 0)} +
+

+ Total earnings +

+ + )} +
+
+ + + + Growth + + + + {isLoading ? ( +
...
+ ) : ( + <> +
+ {stats?.growthPercentage !== undefined + ? `${stats.growthPercentage > 0 ? '+' : ''}${stats.growthPercentage.toFixed(1)}%` + : 'N/A' + } +
+

+ vs last month +

+ + )}
+ + {stats?.recentActivity && stats.recentActivity.length > 0 && ( + + + Recent Activity + + +
+ {stats.recentActivity.map((activity) => ( +
+
+

{activity.description}

+

+ {new Date(activity.date).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + })} +

+
+ {activity.amount && ( +
+

{formatCurrency(activity.amount)}

+
+ )} +
+ ))} +
+
+
+ )}
) } diff --git a/src/pages/login/__tests__/index.test.tsx b/src/pages/login/__tests__/index.test.tsx index 7248b64..3c0afb2 100644 --- a/src/pages/login/__tests__/index.test.tsx +++ b/src/pages/login/__tests__/index.test.tsx @@ -2,11 +2,11 @@ import { describe, it, expect, vi } from 'vitest' import { render, screen, waitFor } from '@/test/test-utils' import userEvent from '@testing-library/user-event' import LoginPage from '../index' -import { adminApiHelpers } from '@/lib/api-client' +import { authService } from '@/services' -// Mock the API client -vi.mock('@/lib/api-client', () => ({ - adminApiHelpers: { +// Mock the service layer +vi.mock('@/services', () => ({ + authService: { login: vi.fn(), }, })) @@ -52,20 +52,19 @@ describe('LoginPage', () => { it('should handle form submission', async () => { const user = userEvent.setup() - const mockLogin = vi.mocked(adminApiHelpers.login) + const mockLogin = vi.mocked(authService.login) mockLogin.mockResolvedValue({ - data: { - access_token: 'fake-token', - user: { - id: '1', - email: 'admin@example.com', - role: 'ADMIN', - firstName: 'Admin', - lastName: 'User', - }, + accessToken: 'fake-token', + refreshToken: 'fake-refresh-token', + user: { + id: '1', + email: 'admin@example.com', + role: 'ADMIN', + firstName: 'Admin', + lastName: 'User', }, - } as any) + }) render() @@ -87,18 +86,19 @@ describe('LoginPage', () => { it('should show error for non-admin users', async () => { const user = userEvent.setup() - const mockLogin = vi.mocked(adminApiHelpers.login) + const mockLogin = vi.mocked(authService.login) mockLogin.mockResolvedValue({ - data: { - access_token: 'fake-token', - user: { - id: '1', - email: 'user@example.com', - role: 'USER', // Not ADMIN - }, + accessToken: 'fake-token', + refreshToken: 'fake-refresh-token', + user: { + id: '1', + email: 'user@example.com', + firstName: 'User', + lastName: 'Test', + role: 'USER', // Not ADMIN }, - } as any) + }) render() diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 3d3f5bb..27db5d1 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Eye, EyeOff } from "lucide-react" import { toast } from "sonner" -import { adminApiHelpers } from "@/lib/api-client" +import { authService } from "@/services" import { errorTracker } from "@/lib/error-tracker" export default function LoginPage() { @@ -24,58 +24,37 @@ export default function LoginPage() { setIsLoading(true) try { - const response = await adminApiHelpers.login({ email, password }) - console.log('Login response:', response.data) // Debug log + const response = await authService.login({ email, password }) - // Handle different response formats - const responseData = response.data - const access_token = responseData.access_token || responseData.token || responseData.accessToken - const refresh_token = responseData.refresh_token || responseData.refreshToken - const user = responseData.user || responseData.data?.user || responseData - - console.log('Extracted token:', access_token) // Debug log - console.log('Extracted user:', user) // Debug log - // Check if user is admin - if (user.role !== 'ADMIN') { + if (response.user.role !== 'ADMIN') { toast.error("Access denied. Admin privileges required.") setIsLoading(false) return } - // Store tokens and user data - if (access_token) { - localStorage.setItem('access_token', access_token) - console.log('Access token stored in localStorage') // Debug log - } else { - console.warn('No access_token in response - assuming httpOnly cookies') // Debug log - } - - if (refresh_token) { - localStorage.setItem('refresh_token', refresh_token) - console.log('Refresh token stored in localStorage') // Debug log - } - - localStorage.setItem('user', JSON.stringify(user)) - console.log('User stored in localStorage') // Debug log + // Set user context for error tracking + errorTracker.setUser({ + id: response.user.id, + email: response.user.email, + name: `${response.user.firstName} ${response.user.lastName}`, + }) // Show success message toast.success("Login successful!") - // Small delay to ensure localStorage is persisted - await new Promise(resolve => setTimeout(resolve, 100)) - - // Verify token is stored before navigation - const storedToken = localStorage.getItem('access_token') - console.log('Token verification before navigation:', storedToken) // Debug log - // Navigate to dashboard - console.log('Navigating to:', from) // Debug log navigate(from, { replace: true }) } catch (error: any) { - console.error('Login error:', error) // Debug log + console.error('Login error:', error) const message = error.response?.data?.message || "Invalid email or password" toast.error(message) + + // Track login error + errorTracker.trackError(error, { + extra: { email, action: 'login' } + }) + setIsLoading(false) } } diff --git a/src/services/analytics.service.ts b/src/services/analytics.service.ts new file mode 100644 index 0000000..243c7e5 --- /dev/null +++ b/src/services/analytics.service.ts @@ -0,0 +1,107 @@ +import apiClient from './api/client' + +export interface OverviewStats { + users?: { + total: number + active: number + inactive: number + } + invoices?: { + total: number + } + revenue?: { + total: number + } + storage?: { + totalSize: number + documents: number + } + totalUsers: number + activeUsers: number + totalRevenue: number + totalTransactions: number + storageUsed: number + storageLimit: number +} + +export interface UserGrowthData { + date: string + users: number + activeUsers: number +} + +export interface RevenueData { + date: string + revenue: number + transactions: number +} + +class AnalyticsService { + /** + * Get overview statistics + */ + async getOverview(): Promise { + const response = await apiClient.get('/admin/analytics/overview') + return response.data + } + + /** + * Get user growth data + */ + async getUserGrowth(days: number = 30): Promise { + const response = await apiClient.get('/admin/analytics/users/growth', { + params: { days }, + }) + return response.data + } + + /** + * Get revenue data + */ + async getRevenue(period: '7days' | '30days' | '90days' = '30days'): Promise { + const response = await apiClient.get('/admin/analytics/revenue', { + params: { period }, + }) + return response.data + } + + /** + * Get API usage statistics + */ + async getApiUsage(days: number = 7): Promise { + const response = await apiClient.get('/admin/analytics/api-usage', { + params: { days }, + }) + return response.data + } + + /** + * Get error rate statistics + */ + async getErrorRate(days: number = 7): Promise { + const response = await apiClient.get('/admin/analytics/error-rate', { + params: { days }, + }) + return response.data + } + + /** + * Get storage usage by user + */ + async getStorageByUser(limit: number = 10): Promise { + const response = await apiClient.get('/admin/analytics/storage/by-user', { + params: { limit }, + }) + return response.data + } + + /** + * Get storage analytics + */ + async getStorageAnalytics(): Promise { + const response = await apiClient.get('/admin/analytics/storage') + return response.data + } +} + +export const analyticsService = new AnalyticsService() diff --git a/src/services/announcement.service.ts b/src/services/announcement.service.ts new file mode 100644 index 0000000..796adcd --- /dev/null +++ b/src/services/announcement.service.ts @@ -0,0 +1,88 @@ +import apiClient from './api/client' + +export interface Announcement { + id: string + title: string + message: string + type: 'info' | 'warning' | 'success' | 'error' + priority: number + targetAudience: string + isActive: boolean + startsAt?: string + endsAt?: string + createdAt: string + updatedAt: string +} + +export interface CreateAnnouncementData { + title: string + message: string + type?: 'info' | 'warning' | 'success' | 'error' + priority?: number + targetAudience?: string + startsAt?: string + endsAt?: string +} + +export interface UpdateAnnouncementData { + title?: string + message?: string + type?: 'info' | 'warning' | 'success' | 'error' + priority?: number + targetAudience?: string + startsAt?: string + endsAt?: string +} + +class AnnouncementService { + /** + * Get all announcements + */ + async getAnnouncements(activeOnly: boolean = false): Promise { + const response = await apiClient.get('/admin/announcements', { + params: { activeOnly }, + }) + return response.data + } + + /** + * Get single announcement by ID + */ + async getAnnouncement(id: string): Promise { + const response = await apiClient.get(`/admin/announcements/${id}`) + return response.data + } + + /** + * Create new announcement + */ + async createAnnouncement(data: CreateAnnouncementData): Promise { + const response = await apiClient.post('/admin/announcements', data) + return response.data + } + + /** + * Update announcement + */ + async updateAnnouncement(id: string, data: UpdateAnnouncementData): Promise { + const response = await apiClient.put(`/admin/announcements/${id}`, data) + return response.data + } + + /** + * Toggle announcement active status + */ + async toggleAnnouncement(id: string): Promise { + const response = await apiClient.patch(`/admin/announcements/${id}/toggle`) + return response.data + } + + /** + * Delete announcement + */ + async deleteAnnouncement(id: string): Promise { + await apiClient.delete(`/admin/announcements/${id}`) + } +} + +export const announcementService = new AnnouncementService() diff --git a/src/services/api/client.ts b/src/services/api/client.ts new file mode 100644 index 0000000..e1c2971 --- /dev/null +++ b/src/services/api/client.ts @@ -0,0 +1,71 @@ +import axios, { type AxiosInstance, type AxiosError } from 'axios' + +const API_BASE_URL = import.meta.env.VITE_BACKEND_API_URL || 'http://localhost:3001/api/v1' + +// Create axios instance with default config +const apiClient: AxiosInstance = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, + withCredentials: true, // Send cookies with requests + timeout: 30000, // 30 second timeout +}) + +// Request interceptor - Add auth token +apiClient.interceptors.request.use( + (config) => { + // Add token from localStorage as fallback (cookies are preferred) + const token = localStorage.getItem('access_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// Response interceptor - Handle errors and token refresh +apiClient.interceptors.response.use( + (response) => response, + async (error: AxiosError) => { + const originalRequest = error.config as any + + // Handle 401 Unauthorized - Try to refresh token + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true + + try { + // Try to refresh token + const refreshToken = localStorage.getItem('refresh_token') + if (refreshToken) { + const response = await axios.post( + `${API_BASE_URL}/auth/refresh`, + { refreshToken }, + { withCredentials: true } + ) + + const { accessToken } = response.data + localStorage.setItem('access_token', accessToken) + + // Retry original request with new token + originalRequest.headers.Authorization = `Bearer ${accessToken}` + return apiClient(originalRequest) + } + } catch (refreshError) { + // Refresh failed - logout user + localStorage.removeItem('access_token') + localStorage.removeItem('refresh_token') + localStorage.removeItem('user') + window.location.href = '/login' + return Promise.reject(refreshError) + } + } + + return Promise.reject(error) + } +) + +export default apiClient diff --git a/src/services/audit.service.ts b/src/services/audit.service.ts new file mode 100644 index 0000000..e08d122 --- /dev/null +++ b/src/services/audit.service.ts @@ -0,0 +1,101 @@ +import apiClient from './api/client' + +export interface AuditLog { + id: string + userId: string + action: string + resourceType: string + resourceId: string + changes?: Record + ipAddress: string + userAgent: string + timestamp: string +} + +export interface GetAuditLogsParams { + page?: number + limit?: number + userId?: string + action?: string + resourceType?: string + resourceId?: string + startDate?: string + endDate?: string + search?: string +} + +export interface AuditStats { + totalActions: number + uniqueUsers: number + topActions: Array<{ action: string; count: number }> + topUsers: Array<{ userId: string; count: number }> +} + +class AuditService { + /** + * Get audit logs with pagination and filters + */ + async getAuditLogs(params?: GetAuditLogsParams): Promise<{ + data: AuditLog[] + total: number + page: number + limit: number + totalPages: number + }> { + const response = await apiClient.get('/admin/audit/logs', { params }) + return response.data + } + + /** + * Get audit log by ID + */ + async getAuditLog(id: string): Promise { + const response = await apiClient.get(`/admin/audit/logs/${id}`) + return response.data + } + + /** + * Get user audit activity + */ + async getUserAuditActivity(userId: string, days: number = 30): Promise { + const response = await apiClient.get(`/admin/audit/users/${userId}`, { + params: { days }, + }) + return response.data + } + + /** + * Get resource history + */ + async getResourceHistory(type: string, id: string): Promise { + const response = await apiClient.get(`/admin/audit/resource/${type}/${id}`) + return response.data + } + + /** + * Get audit statistics + */ + async getAuditStats(startDate?: string, endDate?: string): Promise { + const response = await apiClient.get('/admin/audit/stats', { + params: { startDate, endDate }, + }) + return response.data + } + + /** + * Export audit logs + */ + async exportAuditLogs(params?: { + format?: 'csv' | 'json' + startDate?: string + endDate?: string + }): Promise { + const response = await apiClient.get('/admin/audit/export', { + params, + responseType: 'blob', + }) + return response.data + } +} + +export const auditService = new AuditService() diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts new file mode 100644 index 0000000..55d45df --- /dev/null +++ b/src/services/auth.service.ts @@ -0,0 +1,99 @@ +import apiClient from './api/client' + +export interface LoginRequest { + email: string + password: string +} + +export interface LoginResponse { + accessToken: string + refreshToken: string + user: { + id: string + email: string + firstName: string + lastName: string + role: string + } +} + +export interface RefreshTokenResponse { + accessToken: string +} + +class AuthService { + /** + * Login user with email and password + */ + async login(credentials: LoginRequest): Promise { + const response = await apiClient.post('/auth/login', credentials) + + // Store tokens + if (response.data.accessToken) { + localStorage.setItem('access_token', response.data.accessToken) + } + if (response.data.refreshToken) { + localStorage.setItem('refresh_token', response.data.refreshToken) + } + if (response.data.user) { + localStorage.setItem('user', JSON.stringify(response.data.user)) + } + + return response.data + } + + /** + * Logout user + */ + async logout(): Promise { + try { + await apiClient.post('/auth/logout') + } finally { + // Clear local storage even if API call fails + localStorage.removeItem('access_token') + localStorage.removeItem('refresh_token') + localStorage.removeItem('user') + } + } + + /** + * Refresh access token + */ + async refreshToken(): Promise { + const refreshToken = localStorage.getItem('refresh_token') + const response = await apiClient.post('/auth/refresh', { + refreshToken, + }) + + if (response.data.accessToken) { + localStorage.setItem('access_token', response.data.accessToken) + } + + return response.data + } + + /** + * Get current user from localStorage + */ + getCurrentUser() { + const userStr = localStorage.getItem('user') + return userStr ? JSON.parse(userStr) : null + } + + /** + * Check if user is authenticated + */ + isAuthenticated(): boolean { + return !!localStorage.getItem('access_token') + } + + /** + * Check if user is admin + */ + isAdmin(): boolean { + const user = this.getCurrentUser() + return user?.role === 'ADMIN' + } +} + +export const authService = new AuthService() diff --git a/src/services/dashboard.service.ts b/src/services/dashboard.service.ts new file mode 100644 index 0000000..c652e79 --- /dev/null +++ b/src/services/dashboard.service.ts @@ -0,0 +1,54 @@ +import apiClient from './api/client' + +export interface UserDashboardStats { + totalInvoices: number + totalTransactions: number + totalRevenue: number + pendingInvoices: number + growthPercentage?: number + recentActivity?: Array<{ + id: string + type: string + description: string + date: string + amount?: number + }> +} + +export interface UserProfile { + id: string + email: string + firstName: string + lastName: string + role: string +} + +class DashboardService { + /** + * Get current user profile + */ + async getUserProfile(): Promise { + const response = await apiClient.get('/user/profile') + return response.data + } + + /** + * Get user dashboard statistics + */ + async getUserStats(): Promise { + const response = await apiClient.get('/user/stats') + return response.data + } + + /** + * Get user recent activity + */ + async getRecentActivity(limit: number = 10): Promise { + const response = await apiClient.get('/user/activity', { + params: { limit }, + }) + return response.data + } +} + +export const dashboardService = new DashboardService() diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..c07ede8 --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,21 @@ +// Export all services from a single entry point +export { authService } from './auth.service' +export { userService } from './user.service' +export { analyticsService } from './analytics.service' +export { securityService } from './security.service' +export { systemService } from './system.service' +export { announcementService } from './announcement.service' +export { auditService } from './audit.service' +export { settingsService } from './settings.service' +export { dashboardService } from './dashboard.service' + +// Export types +export type { LoginRequest, LoginResponse } from './auth.service' +export type { User, GetUsersParams, PaginatedResponse } from './user.service' +export type { OverviewStats, UserGrowthData, RevenueData } from './analytics.service' +export type { SuspiciousActivity, ActiveSession, FailedLogin, ApiKey } from './security.service' +export type { HealthStatus, SystemInfo, MaintenanceStatus } from './system.service' +export type { Announcement, CreateAnnouncementData, UpdateAnnouncementData } from './announcement.service' +export type { AuditLog, GetAuditLogsParams, AuditStats } from './audit.service' +export type { Setting, CreateSettingData, UpdateSettingData } from './settings.service' +export type { UserDashboardStats, UserProfile } from './dashboard.service' diff --git a/src/services/security.service.ts b/src/services/security.service.ts new file mode 100644 index 0000000..27f72e3 --- /dev/null +++ b/src/services/security.service.ts @@ -0,0 +1,112 @@ +import apiClient from './api/client' + +export interface SuspiciousActivity { + id: string + userId: string + type: string + description: string + ipAddress: string + timestamp: string + severity: 'low' | 'medium' | 'high' +} + +export interface ActiveSession { + id: string + userId: string + ipAddress: string + userAgent: string + lastActivity: string + createdAt: string +} + +export interface FailedLogin { + id: string + email: string + ipAddress: string + timestamp: string + reason: string +} + +export interface ApiKey { + id: string + name: string + key: string + userId: string + createdAt: string + lastUsed?: string + isActive: boolean +} + +class SecurityService { + /** + * Get suspicious activity logs + */ + async getSuspiciousActivity(): Promise<{ + suspiciousIPs?: any[] + suspiciousEmails?: any[] + }> { + const response = await apiClient.get('/admin/security/suspicious') + return response.data + } + + /** + * Get active user sessions + */ + async getActiveSessions(): Promise { + const response = await apiClient.get('/admin/security/sessions') + return response.data + } + + /** + * Terminate a user session + */ + async terminateSession(sessionId: string): Promise { + await apiClient.delete(`/admin/security/sessions/${sessionId}`) + } + + /** + * Get failed login attempts + */ + async getFailedLogins(params?: { + page?: number + limit?: number + email?: string + }): Promise<{ data: FailedLogin[]; total: number }> { + const response = await apiClient.get('/admin/security/failed-logins', { params }) + return response.data + } + + /** + * Get rate limit violations + */ + async getRateLimitViolations(days: number = 7): Promise { + const response = await apiClient.get('/admin/security/rate-limits', { + params: { days }, + }) + return response.data + } + + /** + * Get all API keys + */ + async getAllApiKeys(): Promise { + const response = await apiClient.get('/admin/security/api-keys') + return response.data + } + + /** + * Revoke an API key + */ + async revokeApiKey(id: string): Promise { + await apiClient.delete(`/admin/security/api-keys/${id}`) + } + + /** + * Ban an IP address + */ + async banIpAddress(ipAddress: string, reason: string): Promise { + await apiClient.post('/admin/security/ban-ip', { ipAddress, reason }) + } +} + +export const securityService = new SecurityService() diff --git a/src/services/settings.service.ts b/src/services/settings.service.ts new file mode 100644 index 0000000..3b5a77d --- /dev/null +++ b/src/services/settings.service.ts @@ -0,0 +1,78 @@ +import apiClient from './api/client' + +export interface Setting { + key: string + value: string + category: string + description?: string + isPublic: boolean + createdAt: string + updatedAt: string +} + +export interface CreateSettingData { + key: string + value: string + category: string + description?: string + isPublic?: boolean +} + +export interface UpdateSettingData { + value: string + description?: string + isPublic?: boolean +} + +class SettingsService { + /** + * Get all settings, optionally filtered by category + */ + async getSettings(category?: string): Promise { + const response = await apiClient.get('/admin/system/settings', { + params: { category }, + }) + return response.data + } + + /** + * Get single setting by key + */ + async getSetting(key: string): Promise { + const response = await apiClient.get(`/admin/system/settings/${key}`) + return response.data + } + + /** + * Create new setting + */ + async createSetting(data: CreateSettingData): Promise { + const response = await apiClient.post('/admin/system/settings', data) + return response.data + } + + /** + * Update setting + */ + async updateSetting(key: string, data: UpdateSettingData): Promise { + const response = await apiClient.put(`/admin/system/settings/${key}`, data) + return response.data + } + + /** + * Delete setting + */ + async deleteSetting(key: string): Promise { + await apiClient.delete(`/admin/system/settings/${key}`) + } + + /** + * Get public settings (for frontend use) + */ + async getPublicSettings(): Promise> { + const response = await apiClient.get>('/settings/public') + return response.data + } +} + +export const settingsService = new SettingsService() diff --git a/src/services/system.service.ts b/src/services/system.service.ts new file mode 100644 index 0000000..a088ccb --- /dev/null +++ b/src/services/system.service.ts @@ -0,0 +1,94 @@ +import apiClient from './api/client' + +export interface HealthStatus { + status: 'healthy' | 'degraded' | 'down' + database: 'connected' | 'disconnected' + redis: 'connected' | 'disconnected' + uptime: number + timestamp: string + recentErrors?: number + activeUsers?: number +} + +export interface SystemInfo { + version: string + environment: string + nodeVersion: string + memory: { + used: number + total: number + } + cpu: { + usage: number + cores: number + loadAverage?: number[] + } + platform?: string + architecture?: string + uptime?: number + env?: string +} + +export interface MaintenanceStatus { + enabled: boolean + message?: string + scheduledStart?: string + scheduledEnd?: string +} + +class SystemService { + /** + * Get system health status + */ + async getHealth(): Promise { + const response = await apiClient.get('/admin/system/health') + return response.data + } + + /** + * Get system information + */ + async getSystemInfo(): Promise { + const response = await apiClient.get('/admin/system/info') + return response.data + } + + /** + * Get maintenance mode status + */ + async getMaintenanceStatus(): Promise { + const response = await apiClient.get('/admin/maintenance/status') + return response.data + } + + /** + * Enable maintenance mode + */ + async enableMaintenance(message?: string): Promise { + await apiClient.post('/admin/maintenance/enable', { message }) + } + + /** + * Disable maintenance mode + */ + async disableMaintenance(): Promise { + await apiClient.post('/admin/maintenance/disable') + } + + /** + * Clear application cache + */ + async clearCache(): Promise { + await apiClient.post('/admin/system/clear-cache') + } + + /** + * Run database migrations + */ + async runMigrations(): Promise<{ success: boolean; message: string }> { + const response = await apiClient.post('/admin/system/migrate') + return response.data + } +} + +export const systemService = new SystemService() diff --git a/src/services/user.service.ts b/src/services/user.service.ts new file mode 100644 index 0000000..7d4998b --- /dev/null +++ b/src/services/user.service.ts @@ -0,0 +1,132 @@ +import apiClient from './api/client' + +export interface User { + id: string + email: string + firstName: string + lastName: string + role: string + isActive: boolean + createdAt: string + updatedAt?: string + lastLogin?: string + _count?: { + invoices?: number + transactions?: number + documents?: number + activityLogs?: number + reports?: number + payments?: number + } +} + +export interface GetUsersParams { + page?: number + limit?: number + search?: string + role?: string + isActive?: boolean +} + +export interface PaginatedResponse { + data: T[] + total: number + page: number + limit: number + totalPages: number +} + +class UserService { + /** + * Get paginated list of users + */ + async getUsers(params?: GetUsersParams): Promise> { + const response = await apiClient.get>('/admin/users', { params }) + return response.data + } + + /** + * Get single user by ID + */ + async getUser(id: string): Promise { + const response = await apiClient.get(`/admin/users/${id}`) + return response.data + } + + /** + * Create new user + */ + async createUser(data: Partial): Promise { + const response = await apiClient.post('/admin/users', data) + return response.data + } + + /** + * Update user + */ + async updateUser(id: string, data: Partial): Promise { + const response = await apiClient.patch(`/admin/users/${id}`, data) + return response.data + } + + /** + * Delete user (soft or hard delete) + */ + async deleteUser(id: string, hard: boolean = false): Promise { + await apiClient.delete(`/admin/users/${id}`, { + params: { hard }, + }) + } + + /** + * Reset user password + */ + async resetPassword(id: string): Promise<{ temporaryPassword: string }> { + const response = await apiClient.post<{ temporaryPassword: string }>( + `/admin/users/${id}/reset-password` + ) + return response.data + } + + /** + * Get user activity logs + */ + async getUserActivity(id: string, days: number = 30): Promise { + const response = await apiClient.get(`/admin/users/${id}/activity`, { + params: { days }, + }) + return response.data + } + + /** + * Import users from CSV + */ + async importUsers(file: File): Promise<{ imported: number; failed: number }> { + const formData = new FormData() + formData.append('file', file) + + const response = await apiClient.post<{ imported: number; failed: number }>( + '/admin/users/import', + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + } + ) + return response.data + } + + /** + * Export users to CSV + */ + async exportUsers(format: 'csv' | 'json' = 'csv'): Promise { + const response = await apiClient.get('/admin/users/export', { + params: { format }, + responseType: 'blob', + }) + return response.data + } +} + +export const userService = new UserService()