6.0 KiB
6.0 KiB
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
# 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
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:
import { useQuery } from '@tanstack/react-query'
import { userService } from '@/services'
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => userService.getUsers({ page: 1, limit: 20 })
})
Mutations:
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:
import { authService } from '@/services'
const response = await authService.login({ email, password })
Authentication
Setup
- Backend must return tokens on login
- Frontend stores in httpOnly cookies (recommended) or localStorage
- All requests automatically include auth token
- 401 errors trigger automatic token refresh
Login Flow
// 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
<Route element={<ProtectedRoute />}>
<Route path="/admin/*" element={<AdminLayout />} />
</Route>
Logout
await authService.logout() // Clears tokens & cookies
navigate('/login')
API Standards
Service Methods
All service methods:
- Return typed data (no
response.dataunwrapping needed) - Throw errors with
error.response.data.message - Use consistent naming (get, create, update, delete)
Error Handling
try {
await userService.deleteUser(id)
toast.success('User deleted')
} catch (error: any) {
toast.error(error.response?.data?.message || 'Operation failed')
}
Type Safety
// All responses are typed
const users: PaginatedResponse<User> = await userService.getUsers()
const stats: OverviewStats = await analyticsService.getOverview()
Environment Variables
# 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
// src/services/user.service.ts
async exportUserData(userId: string): Promise<Blob> {
const response = await apiClient.get(`/admin/users/${userId}/export`, {
responseType: 'blob'
})
return response.data
}
Adding a New Page
- Create page component in
src/pages/ - Add route in
src/App.tsx - Import required services
- Use React Query for data fetching
Adding a New Component
- Create in
src/components/ - Use TypeScript for props
- Follow existing patterns
- Add to component exports if reusable
Best Practices
React Query
// Good - specific query keys
queryKey: ['users', page, limit, search]
// Bad - too generic
queryKey: ['data']
Service Layer
// Good - use services
import { userService } from '@/services'
await userService.getUsers()
// Bad - direct axios
import axios from 'axios'
await axios.get('/api/users')
Error Handling
// 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
// Good - use types
const users: PaginatedResponse<User> = await userService.getUsers()
// Bad - any type
const users: any = await userService.getUsers()
Troubleshooting
CORS Errors
- Ensure backend has CORS configured
- Check
withCredentials: truein API client - Verify
VITE_BACKEND_API_URLis correct
401 Errors
- Check token is being sent
- Verify backend token validation
- Check token expiration
Build Errors
- Run
npm run buildto check TypeScript errors - Fix any type errors
- Ensure all imports are correct
Test Failures
- Run
npm run testto see failures - Check mock implementations
- Verify test data matches types