# Admin Trade Control - Web UI Implementation

## Overview

This document describes the **frontend implementation** of the Admin Trade Control feature in the trading dashboard. The UI allows authorized administrators to pause and resume auto-trading with clear visual indicators.

For backend implementation details, see the backend service documentation.

---

## UI Components

### 1. Header Status Badge (Global)

**Location**: `src/App.tsx` (lines 195-231)

**Visibility**: All pages, all users (read-only)

**Features**:
- Shows current trading state: PAUSED or RUNNING
- Color-coded: Orange (paused) / Green (running)
- Icon indicators: ⏸️ (paused) / ▶️ (running)
- Tooltip with who paused and when
- Updates in real-time via WebSocket

**Implementation**:
```tsx
{botState.health?.tradingControl && (
  <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '0.5rem',
    padding: '0.4rem 0.8rem',
    background: botState.health.tradingControl.mode === 'PAUSED' 
      ? 'rgba(255,149,0,0.15)' 
      : 'rgba(52,199,89,0.08)',
    border: `1px solid ${botState.health.tradingControl.mode === 'PAUSED' 
      ? 'rgba(255,149,0,0.4)' 
      : 'rgba(52,199,89,0.3)'}`,
    borderRadius: '8px'
  }}
  title={botState.health.tradingControl.mode === 'PAUSED' 
    ? `Trading paused by ${botState.health.tradingControl.lastChangedBy}. No new entries will be placed.` 
    : 'Auto-trading is active'}
  >
    <span style={{ fontSize: '0.85rem' }}>
      {botState.health.tradingControl.mode === 'PAUSED' ? '⏸️' : '▶️'}
    </span>
    <div style={{ display: 'flex', flexDirection: 'column' }}>
      <span style={{
        fontSize: '0.75rem',
        fontWeight: '900',
        color: botState.health.tradingControl.mode === 'PAUSED' ? '#ff9500' : '#34c759',
        letterSpacing: '0.05em',
        textTransform: 'uppercase'
      }}>
        {botState.health.tradingControl.mode === 'PAUSED' ? 'Trading Paused' : 'Trading Active'}
      </span>
      <span style={{ fontSize: '10px', color: '#666', fontWeight: 'bold' }}>
        {botState.health.tradingControl.mode === 'PAUSED' ? 'No new entries' : 'Entries allowed'}
      </span>
    </div>
  </div>
)}
```

---

### 2. Admin Tab Controls (Admin Only)

**Location**: `src/tabs/AdminTab.tsx` (lines 295-380)

**Visibility**: Admin users only (role = 'admin')

**Features**:
- Trading Control Panel section
- Status banner showing current mode
- Pause/Resume buttons
- Error display
- Safety notice
- Loading states
- Disabled states

**State Management**:
```tsx
const [isControlLoading, setIsControlLoading] = React.useState(false);
const [controlError, setControlError] = React.useState<string | null>(null);
const tradingControl = botState.health?.tradingControl;
const isPaused = tradingControl?.mode === 'PAUSED';
```

**Pause Handler**:
```tsx
const handlePauseTrading = async () => {
    setIsControlLoading(true);
    setControlError(null);
    try {
        const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:5000';
        const { data: sessionData } = await supabase.auth.getSession();
        const accessToken = sessionData.session?.access_token;
        if (!accessToken) {
            throw new Error('Not authenticated');
        }
        const res = await fetch(`${apiUrl}/internal/trading/pause`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${accessToken}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ reason: 'Admin pause from dashboard' })
        });
        if (!res.ok) {
            const errorData = await res.json().catch(() => ({ error: 'Unknown error' }));
            throw new Error(errorData.error || `HTTP ${res.status}`);
        }
    } catch (err: any) {
        setControlError(err.message || 'Failed to pause trading');
    } finally {
        setIsControlLoading(false);
    }
};
```

**Resume Handler**:
```tsx
const handleResumeTrading = async () => {
    setIsControlLoading(true);
    setControlError(null);
    try {
        const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:5000';
        const { data: sessionData } = await supabase.auth.getSession();
        const accessToken = sessionData.session?.access_token;
        if (!accessToken) {
            throw new Error('Not authenticated');
        }
        const res = await fetch(`${apiUrl}/internal/trading/resume`, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${accessToken}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ reason: 'Admin resume from dashboard' })
        });
        if (!res.ok) {
            const errorData = await res.json().catch(() => ({ error: 'Unknown error' }));
            throw new Error(errorData.error || `HTTP ${res.status}`);
        }
    } catch (err: any) {
        setControlError(err.message || 'Failed to resume trading');
    } finally {
        setIsControlLoading(false);
    }
};
```

**UI Rendering**:
```tsx
{/* Status Banner */}
<div className={`flex items-center justify-between px-4 py-3 rounded-xl border ${
    isPaused 
        ? 'bg-orange-500/[0.06] border-orange-500/20' 
        : 'bg-emerald-500/[0.06] border-emerald-500/20'
}`}>
    <div className="flex items-center gap-3">
        {isPaused ? (
            <Pause size={16} className="text-orange-400" />
        ) : (
            <Play size={16} className="text-emerald-400" />
        )}
        <div>
            <p className={`text-sm font-bold ${isPaused ? 'text-orange-300' : 'text-emerald-300'}`}>
                AUTO-TRADING: {isPaused ? 'PAUSED' : 'RUNNING'}
            </p>
            <p className="text-[10px] text-zinc-500 mt-0.5">
                {isPaused 
                    ? 'No new positions will be opened. Existing positions are still managed.' 
                    : 'Bot is actively monitoring and executing trades based on strategy rules.'}
            </p>
        </div>
    </div>
    {tradingControl && (
        <div className="text-right">
            <p className="text-[9px] text-zinc-500 uppercase tracking-wider">Last Changed</p>
            <p className="text-[10px] text-zinc-400 font-mono">
                {new Date(tradingControl.lastChangedAt).toLocaleString()}
            </p>
            <p className="text-[9px] text-zinc-600">by {tradingControl.lastChangedBy}</p>
        </div>
    )}
</div>

{/* Control Buttons */}
<div className="grid grid-cols-2 gap-3">
    <button
        onClick={handlePauseTrading}
        disabled={isPaused || isControlLoading}
        className={/* ... */}
        title={isPaused ? "Trading is already paused" : "Pause all new trade entries"}
    >
        <Pause size={16} />
        {isControlLoading && !isPaused ? 'Pausing...' : 'Pause Auto Trading'}
    </button>
    <button
        onClick={handleResumeTrading}
        disabled={!isPaused || isControlLoading}
        className={/* ... */}
        title={!isPaused ? "Trading is already running" : "Resume automated trade execution"}
    >
        <Play size={16} />
        {isControlLoading && isPaused ? 'Resuming...' : 'Resume Auto Trading'}
    </button>
</div>

{/* Error Display */}
{controlError && (
    <div className="flex items-center gap-2 px-4 py-3 bg-red-500/[0.06] border border-red-500/20 rounded-xl">
        <AlertTriangle size={14} className="text-red-400 shrink-0" />
        <p className="text-xs text-red-400">{controlError}</p>
    </div>
)}

{/* Safety Notice */}
<div className="flex items-start gap-2 px-4 py-3 bg-blue-500/[0.04] border border-blue-500/10 rounded-xl">
    <AlertTriangle size={12} className="text-blue-400 shrink-0 mt-0.5" />
    <p className="text-[10px] text-blue-400/80 leading-relaxed">
        <strong>Safety Note:</strong> Pausing trading blocks new entries only. 
        Existing positions will continue to be monitored for exits, stop-losses, and take-profits.
    </p>
</div>
```

---

### 3. Database Synchronization Control (Admin Only)

**Location**: `src/tabs/AdminTab.tsx` (lines 409-480)

**Features**:
- **State Snapshots Toggle**: Enable/Disable all database writes.
- **Sync Interval Slider**: Adjustable frequency (1m to 60m).
- **Audit Logging**: Saves settings to `bot_config` table.

**Implementation**:
```tsx
const [dbSyncEnabled, setDbSyncEnabled] = React.useState<boolean>(true);
const [dbSyncInterval, setDbSyncInterval] = React.useState<number>(300000);

const handleUpdateDbSync = async () => {
    const updates = [
        { key: 'ENABLE_DB_SNAPSHOTS', value: String(dbSyncEnabled) },
        { key: 'DB_SNAPSHOT_INTERVAL_MS', value: String(dbSyncInterval) }
    ];
    await supabase.from('bot_config').upsert(updates);
};
```

---

### 4. WebSocket Integration

**Location**: `src/hooks/useWebSocket.ts`

**Purpose**: Receive real-time trading control state updates from backend

**Implementation**:
```tsx
export interface TradingControlSnapshot {
    mode: 'RUNNING' | 'PAUSED';
    lastChangedBy: string;
    lastChangedAt: number;
    reason?: string;
}

export interface HealthSnapshot {
    tradingLoopHealthy: boolean;
    tradingLoopLastRun: number | null;
    // ... other health metrics
    tradingControl: TradingControlSnapshot;
}

// In useWebSocket hook:
newSocket.on('health_update', (health: HealthSnapshot) => {
    setBotState(prev => ({
        ...prev,
        health
    }));
});
```

**Data Flow**:
1. Admin clicks "Pause" button
2. Frontend calls `POST /internal/trading/pause`
3. Backend updates state and broadcasts `health_update` event
4. WebSocket receives event
5. `botState.health.tradingControl` updates
6. UI re-renders with new state

---

## UI States

### Button States

| Scenario | Pause Button | Resume Button |
|----------|--------------|---------------|
| Trading is RUNNING | Enabled | Disabled |
| Trading is PAUSED | Disabled | Enabled |
| API call in progress | Disabled (shows "Pausing...") | Disabled (shows "Resuming...") |
| API error | Re-enabled | Re-enabled |

### Visual Indicators

| State | Header Badge | Admin Panel Banner |
|-------|--------------|-------------------|
| RUNNING | ▶️ Trading Active (green) | AUTO-TRADING: RUNNING (green) |
| PAUSED | ⏸️ Trading Paused (orange) | AUTO-TRADING: PAUSED (orange) |

---

## Error Handling

### API Errors

**Scenarios**:
- Network failure
- Authentication failure (401)
- Authorization failure (403)
- Server error (500)

**Handling**:
```tsx
try {
    const res = await fetch(/* ... */);
    if (!res.ok) {
        const errorData = await res.json().catch(() => ({ error: 'Unknown error' }));
        throw new Error(errorData.error || `HTTP ${res.status}`);
    }
} catch (err: any) {
    setControlError(err.message || 'Failed to pause trading');
}
```

**Display**:
- Error message shown in red alert box
- User can retry by clicking button again
- Error clears on next successful action

### WebSocket Disconnection

**Behavior**:
- Last-known state remains visible
- Timestamp shows when state was last updated
- Connection status indicator in header shows "Reconnecting..."
- State updates when WebSocket reconnects

---

## TypeScript Types

```tsx
// From useWebSocket.ts
export interface TradingControlSnapshot {
    mode: 'RUNNING' | 'PAUSED';
    lastChangedBy: string;
    lastChangedAt: number;
    reason?: string;
}

export interface HealthSnapshot {
    tradingLoopHealthy: boolean;
    tradingLoopLastRun: number | null;
    monitorLoopHealthy: boolean;
    monitorLoopLastRun: number | null;
    orderSyncHealthy: boolean;
    orderSyncLastRun: number | null;
    lockContentionCount: number;
    reconciliationLoopHealthy: boolean;
    reconciliationLoopLastRun: number | null;
    capitalInvariantViolations: number;
    tradingControl: TradingControlSnapshot;
}

export interface BotState {
    health: HealthSnapshot;
    // ... other state properties
}
```

---

## Styling

### Color Palette

| State | Background | Border | Text |
|-------|-----------|--------|------|
| PAUSED | `rgba(255,149,0,0.15)` | `rgba(255,149,0,0.4)` | `#ff9500` |
| RUNNING | `rgba(52,199,89,0.08)` | `rgba(52,199,89,0.3)` | `#34c759` |
| ERROR | `rgba(255,59,48,0.06)` | `rgba(255,59,48,0.2)` | `#ff3b30` |
| INFO | `rgba(10,132,255,0.04)` | `rgba(10,132,255,0.1)` | `#0a84ff` |

### Icons

- Pause: `⏸️` or `<Pause />` from lucide-react
- Play/Resume: `▶️` or `<Play />` from lucide-react
- Warning: `<AlertTriangle />` from lucide-react

---

## Testing

### Component Tests

**File**: `src/tabs/AdminTab.test.tsx`

```tsx
describe('AdminTab - Trading Control', () => {
    test('should show running status when mode is RUNNING', () => {
        const mockBotState = {
            health: {
                tradingControl: {
                    mode: 'RUNNING',
                    lastChangedBy: 'system',
                    lastChangedAt: Date.now()
                }
            }
        };
        render(<AdminTab botState={mockBotState} />);
        expect(screen.getByText(/AUTO-TRADING: RUNNING/i)).toBeInTheDocument();
    });

    test('should disable pause button when already paused', () => {
        const pausedState = {
            health: {
                tradingControl: { mode: 'PAUSED', lastChangedBy: 'admin', lastChangedAt: Date.now() }
            }
        };
        render(<AdminTab botState={pausedState} />);
        const pauseButton = screen.getByText(/Pause Auto Trading/i);
        expect(pauseButton).toBeDisabled();
    });

    test('should show error when API call fails', async () => {
        global.fetch = jest.fn(() => 
            Promise.resolve({
                ok: false,
                json: () => Promise.resolve({ error: 'Unauthorized' })
            })
        );
        
        render(<AdminTab botState={mockBotState} />);
        const pauseButton = screen.getByText(/Pause Auto Trading/i);
        fireEvent.click(pauseButton);
        
        await waitFor(() => {
            expect(screen.getByText(/Unauthorized/i)).toBeInTheDocument();
        });
    });
});
```

---

## User Guide

### For Admins

**How to Pause Trading**:
1. Login as admin user
2. Navigate to **Admin** tab (🛡️ icon in header)
3. Scroll to **Trading Control** section
4. Click **"Pause Auto Trading"** button
5. Verify header badge shows "⏸️ Trading Paused"
6. Confirm status banner shows "AUTO-TRADING: PAUSED"

**How to Resume Trading**:
1. Navigate to **Admin** tab
2. Click **"Resume Auto Trading"** button
3. Verify header badge shows "▶️ Trading Active"
4. Confirm status banner shows "AUTO-TRADING: RUNNING"

**What Happens When Paused**:
- ❌ No new trade entries will be placed
- ✅ Existing positions continue to be managed
- ✅ Exit orders still execute
- ✅ Stop-loss monitoring continues
- ✅ Take-profit monitoring continues

---

## Troubleshooting

### Issue: Pause button not working

**Check**:
1. User has `role = 'admin'` in profile
2. Valid JWT token in request (check Network tab)
3. Backend API is running
4. Browser console for errors

### Issue: Status not updating in UI

**Check**:
1. WebSocket connection active (header shows "Connected")
2. `health_update` events received (check browser console)
3. Refresh page to force sync
4. Check backend logs for broadcast

### Issue: Error message displayed

**Common Errors**:
- "Not authenticated" → Login again
- "Unauthorized" → User is not admin
- "Failed to fetch" → Backend API not running
- "HTTP 500" → Check backend logs

---

## Files Modified

1. ✅ `src/App.tsx` - Added header status badge
2. ✅ `src/tabs/AdminTab.tsx` - Added Trading Control Panel
3. ✅ `src/hooks/useWebSocket.ts` - Already had health_update handler

---

## Related Documentation

- **Backend Implementation**: See `bytelyst-trading-bot-service/docs/ADMIN_TRADE_CONTROL_IMPLEMENTATION.md`
- **State Persistence**: See `bytelyst-trading-bot-service/docs/TRADING_CONTROL_PERSISTENCE.md`
- **Test Plan**: See `bytelyst-trading-bot-service/docs/ADMIN_TRADE_CONTROL_TEST_PLAN.md`
- **Architecture**: See `bytelyst-trading-bot-service/docs/ADMIN_TRADE_CONTROL_ARCHITECTURE.md`
