984 lines
22 KiB
Markdown
984 lines
22 KiB
Markdown
# 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 <div>Loading...</div>
|
|
if (error) return <div>Error: {error.message}</div>
|
|
|
|
return (
|
|
<div>
|
|
{data?.data.map(user => (
|
|
<div key={user.id}>{user.email}</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
### 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 (
|
|
<button
|
|
onClick={() => mutation.mutate()}
|
|
disabled={mutation.isPending}
|
|
>
|
|
{mutation.isPending ? 'Deleting...' : 'Delete User'}
|
|
</button>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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 (
|
|
<div>
|
|
<input
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
placeholder="Search users..."
|
|
/>
|
|
|
|
{isLoading ? (
|
|
<div>Loading...</div>
|
|
) : (
|
|
<div>
|
|
{data?.data.map(user => (
|
|
<UserCard key={user.id} user={user} />
|
|
))}
|
|
|
|
<Pagination
|
|
page={page}
|
|
totalPages={data?.totalPages}
|
|
onPageChange={setPage}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
### 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 (
|
|
<form onSubmit={handleSubmit}>
|
|
<input
|
|
value={formData.email}
|
|
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
|
placeholder="Email"
|
|
required
|
|
/>
|
|
{/* More fields... */}
|
|
|
|
<button type="submit" disabled={createMutation.isPending}>
|
|
{createMutation.isPending ? 'Creating...' : 'Create User'}
|
|
</button>
|
|
</form>
|
|
)
|
|
}
|
|
```
|
|
|
|
### 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 (
|
|
<button onClick={() => updateMutation.mutate()}>
|
|
Update User
|
|
</button>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Pattern 4: File Upload
|
|
|
|
```typescript
|
|
import { userService } from '@/services'
|
|
|
|
function ImportUsersButton() {
|
|
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
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 (
|
|
<input
|
|
type="file"
|
|
accept=".csv"
|
|
onChange={handleFileUpload}
|
|
/>
|
|
)
|
|
}
|
|
```
|
|
|
|
### 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 (
|
|
<button onClick={handleExport}>
|
|
Export Users
|
|
</button>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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 <div>Error: {error.message}</div>
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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 <div>Loading...</div>
|
|
|
|
return (
|
|
<div>
|
|
<h1>Users Management</h1>
|
|
|
|
{/* User List */}
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Email</th>
|
|
<th>Name</th>
|
|
<th>Role</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{users?.data.map(user => (
|
|
<tr key={user.id}>
|
|
<td>{user.email}</td>
|
|
<td>{user.firstName} {user.lastName}</td>
|
|
<td>{user.role}</td>
|
|
<td>
|
|
<button onClick={() => setEditingUser(user)}>
|
|
Edit
|
|
</button>
|
|
<button onClick={() => deleteMutation.mutate(user.id)}>
|
|
Delete
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
|
|
{/* Pagination */}
|
|
<div>
|
|
<button
|
|
onClick={() => setPage(p => p - 1)}
|
|
disabled={page === 1}
|
|
>
|
|
Previous
|
|
</button>
|
|
<span>Page {page} of {users?.totalPages}</span>
|
|
<button
|
|
onClick={() => setPage(p => p + 1)}
|
|
disabled={page === users?.totalPages}
|
|
>
|
|
Next
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 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
|