fix: operator approval workflow — add approval toggle, lockout CRUD, and PBT tests

- Add GET/PUT /api/admin/trading/approval-config endpoints
- Add POST/DELETE /api/admin/trading/lockouts endpoints
- Add useApprovalConfig, useUpdateApprovalConfig, useCreateLockout, useDeleteLockout hooks
- Add Paper Order Approval toggle card with confirmation dialog
- Add lockout creation form and delete button to Active Lockouts card
- Add MSW handlers for all new endpoints
- Add property-based tests for bug condition exploration and preservation
This commit is contained in:
Celes Renata
2026-04-17 06:14:46 +00:00
parent 3b7ded37cc
commit b149f70507
9 changed files with 1035 additions and 5 deletions
+45 -1
View File
@@ -3,7 +3,7 @@
* Requirements: 13.1, 13.2
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiGet, apiPost, apiPut } from './client';
import { apiGet, apiPost, apiPut, apiDelete } from './client';
import type { ApiBase } from './client';
// ---------------------------------------------------------------------------
@@ -444,6 +444,50 @@ export function useActiveLockouts() {
return useGet<Lockout[]>(['lockouts'], 'query', '/api/admin/trading/lockouts');
}
// ---------------------------------------------------------------------------
// Admin: Approval Config
// ---------------------------------------------------------------------------
export interface ApprovalConfig {
auto_approve_paper: boolean;
require_approval_for_live: boolean;
approval_timeout_minutes: number;
}
export function useApprovalConfig() {
return useGet<ApprovalConfig>(['approval-config'], 'query', '/api/admin/trading/approval-config');
}
export function useUpdateApprovalConfig() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: Partial<ApprovalConfig>) =>
apiPut<ApprovalConfig>('query', '/api/admin/trading/approval-config', body),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['approval-config'] });
qc.invalidateQueries({ queryKey: ['trading-config'] });
},
});
}
export function useCreateLockout() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: { ticker: string; reason: string; duration_minutes: number }) =>
apiPost<Lockout>('query', '/api/admin/trading/lockouts', body),
onSuccess: () => qc.invalidateQueries({ queryKey: ['lockouts'] }),
});
}
export function useDeleteLockout() {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) =>
apiDelete<unknown>('query', `/api/admin/trading/lockouts/${id}`),
onSuccess: () => qc.invalidateQueries({ queryKey: ['lockouts'] }),
});
}
// ---------------------------------------------------------------------------
// Admin: Sources
// ---------------------------------------------------------------------------