Files
crypto_trader/docs/developer/ui_development.md

7.9 KiB

Frontend Development Guide

This guide explains how to develop and extend frontend components in Crypto Trader.

Tech Stack

  • React 18 - UI framework
  • TypeScript - Type safety
  • Material-UI (MUI) - Component library
  • React Query - Data fetching and caching
  • React Router - Routing
  • Vite - Build tool

Project Structure

frontend/
├── src/
│   ├── pages/        # Page components
│   ├── components/   # Reusable components
│   ├── api/         # API client functions
│   ├── hooks/       # Custom React hooks
│   ├── types/       # TypeScript types
│   └── App.tsx      # Main app component
├── public/          # Static assets
└── package.json     # Dependencies

Development Setup

cd frontend
npm install
npm run dev

Access at: http://localhost:3000

Creating a New Page

  1. Create page component in src/pages/:
// src/pages/MyPage.tsx
import { Box, Typography } from '@mui/material';

export function MyPage() {
  return (
    <Box>
      <Typography variant="h4">My Page</Typography>
    </Box>
  );
}
  1. Add route in src/App.tsx:
import { MyPage } from './pages/MyPage';

<Route path="/my-page" element={<MyPage />} />

API Integration

Creating API Functions

// src/api/myService.ts
import { apiClient } from './client';

export const myServiceApi = {
  async getData() {
    const response = await apiClient.get('/api/my-service/data');
    return response.data;
  },
  
  async createItem(data: CreateItemDto) {
    const response = await apiClient.post('/api/my-service/items', data);
    return response.data;
  }
};

Using React Query

import { useQuery, useMutation } from '@tanstack/react-query';
import { myServiceApi } from '@/api/myService';

function MyComponent() {
  // Fetch data
  const { data, isLoading, error } = useQuery({
    queryKey: ['myData'],
    queryFn: myServiceApi.getData
  });
  
  // Mutate data
  const mutation = useMutation({
    mutationFn: myServiceApi.createItem,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['myData'] });
    }
  });
  
  return (
    <div>
      {isLoading && <Loading />}
      {data && <DataDisplay data={data} />}
      <button onClick={() => mutation.mutate(formData)}>Create</button>
    </div>
  );
}

Creating Reusable Components

// src/components/DataTable.tsx
import { Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';

interface DataTableProps<T> {
  data: T[];
  columns: Column<T>[];
}

export function DataTable<T>({ data, columns }: DataTableProps<T>) {
  return (
    <Table>
      <TableHead>
        <TableRow>
          {columns.map(col => (
            <TableCell key={col.key}>{col.header}</TableCell>
          ))}
        </TableRow>
      </TableHead>
      <TableBody>
        {data.map((row, idx) => (
          <TableRow key={idx}>
            {columns.map(col => (
              <TableCell key={col.key}>{col.render(row)}</TableCell>
            ))}
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}

Custom Hooks

// src/hooks/useOrders.ts
import { useQuery, useMutation } from '@tanstack/react-query';
import { tradingApi } from '@/api/trading';

export function useOrders() {
  const { data: orders, isLoading, error } = useQuery({
    queryKey: ['orders'],
    queryFn: () => tradingApi.getOrders()
  });
  
  const placeOrder = useMutation({
    mutationFn: tradingApi.placeOrder,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['orders'] });
    }
  });
  
  return {
    orders: orders ?? [],
    isLoading,
    error,
    placeOrder: placeOrder.mutate
  };
}

WebSocket Integration

The application uses an enhanced WebSocket system with message type handling:

// Using the WebSocket context
import { useWebSocketContext } from '@/components/WebSocketProvider';
import { useRealtimeData } from '@/hooks/useRealtimeData';

function MyComponent() {
  const { isConnected, lastMessage, subscribe } = useWebSocketContext();
  
  // Subscribe to specific message types
  useEffect(() => {
    const unsubscribe = subscribe('order_update', (message) => {
      // Handle order update
      console.log('Order updated:', message);
    });
    
    return unsubscribe;
  }, [subscribe]);
  
  // Or use the real-time data hook for automatic query invalidation
  useRealtimeData(); // Automatically handles common message types
}

Message Types

The WebSocket supports these message types:

  • order_update: Order status changes
  • position_update: Position changes
  • price_update: Price updates
  • alert_triggered: Alert notifications
  • strategy_signal: Strategy signal notifications
  • system_event: System events and errors

Form Handling

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

const schema = yup.object({
  symbol: yup.string().required(),
  quantity: yup.number().positive().required()
});

function OrderForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: yupResolver(schema)
  });
  
  const onSubmit = async (data) => {
    await tradingApi.placeOrder(data);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <TextField
        {...register('symbol')}
        error={!!errors.symbol}
        helperText={errors.symbol?.message}
      />
      <Button type="submit">Place Order</Button>
    </form>
  );
}

Error Handling

The application uses a centralized Snackbar context for error handling:

import { useSnackbar } from '@/contexts/SnackbarContext';
import ErrorDisplay from '@/components/ErrorDisplay';

function MyComponent() {
  const { showError, showSuccess, showWarning, showInfo } = useSnackbar();
  
  const handleAction = async () => {
    try {
      await apiCall();
      showSuccess('Action completed successfully');
    } catch (err) {
      showError(err instanceof Error ? err.message : 'An error occurred');
    }
  };
  
  // For complex error display with retry
  return (
    <>
      {error && (
        <ErrorDisplay
          error={error}
          title="Operation Failed"
          onRetry={handleAction}
        />
      )}
    </>
  );
}

Available Components

  • ErrorDisplay: Enhanced error display with retry functionality
  • LoadingSkeleton: Loading placeholders for tables, cards, lists
  • ProgressOverlay: Overlay with progress indicator
  • StatusIndicator: Connection status indicators
  • DataFreshness: Data freshness timestamps
  • HelpTooltip: Contextual help tooltips
  • InfoCard: Collapsible information cards
  • OperationsPanel: Panel showing running operations

Styling

Material-UI Theming

// src/theme.ts
import { createTheme } from '@mui/material/styles';

export const theme = createTheme({
  palette: {
    mode: 'dark',
    primary: {
      main: '#1976d2'
    }
  }
});

Component Styling

Use Material-UI's sx prop for inline styles:

<Box
  sx={{
    padding: 2,
    backgroundColor: 'background.paper',
    borderRadius: 1
  }}
>
  Content
</Box>

Testing

See Testing Guide for frontend testing strategies.

Best Practices

  1. Type Safety: Always use TypeScript types
  2. Component Composition: Prefer composition over inheritance
  3. Custom Hooks: Extract reusable logic into hooks
  4. Error Handling: Handle errors gracefully with user feedback
  5. Loading States: Always show loading indicators
  6. Accessibility: Use semantic HTML and ARIA labels
  7. Performance: Use React.memo, useMemo, useCallback appropriately
  8. Code Splitting: Use lazy loading for large components
  9. Form Validation: Validate on both client and server
  10. Consistent Patterns: Follow established patterns in the codebase