Yaltopia-Ticket-Admin/dev-docs/DEVELOPMENT.md

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

  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

// 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.data unwrapping 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

  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

// 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: 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