347 lines
7.9 KiB
Markdown
347 lines
7.9 KiB
Markdown
|
|
# 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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd frontend
|
||
|
|
npm install
|
||
|
|
npm run dev
|
||
|
|
```
|
||
|
|
|
||
|
|
Access at: http://localhost:3000
|
||
|
|
|
||
|
|
## Creating a New Page
|
||
|
|
|
||
|
|
1. Create page component in `src/pages/`:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// src/pages/MyPage.tsx
|
||
|
|
import { Box, Typography } from '@mui/material';
|
||
|
|
|
||
|
|
export function MyPage() {
|
||
|
|
return (
|
||
|
|
<Box>
|
||
|
|
<Typography variant="h4">My Page</Typography>
|
||
|
|
</Box>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Add route in `src/App.tsx`:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { MyPage } from './pages/MyPage';
|
||
|
|
|
||
|
|
<Route path="/my-page" element={<MyPage />} />
|
||
|
|
```
|
||
|
|
|
||
|
|
## API Integration
|
||
|
|
|
||
|
|
### Creating API Functions
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 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:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 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:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
<Box
|
||
|
|
sx={{
|
||
|
|
padding: 2,
|
||
|
|
backgroundColor: 'background.paper',
|
||
|
|
borderRadius: 1
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
Content
|
||
|
|
</Box>
|
||
|
|
```
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
See [Testing Guide](./testing.md) 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
|