← back to Bertha
kalshi-dash/src/pages/Orders.jsx
303 lines
import React, { useState, useEffect } from 'react';
import { post } from '../api';
// ── Status badge mapping ──
function statusBadge(status) {
if (status === 'filled') return 'bg-green';
if (status === 'resting') return 'bg-orange';
if (status === 'canceled') return 'bg-red';
return 'bg-blue';
}
// ── Empty state ──
function EmptyState({ label }) {
return (
<tr>
<td colSpan={20}>
<div className="flex flex-col items-center justify-center py-16 gap-3">
<svg
width="40"
height="40"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
style={{ color: 'var(--text-muted)' }}
>
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2" />
<rect x="9" y="3" width="6" height="4" rx="1" />
<path d="M9 12h6M9 16h4" />
</svg>
<p style={{ color: 'var(--text-muted)', fontFamily: 'var(--font-body)', fontSize: '0.875rem' }}>
{label}
</p>
</div>
</td>
</tr>
);
}
// ── Spinner ──
function Spinner() {
return (
<div className="flex items-center justify-center h-64">
<div
className="w-9 h-9 rounded-full border-2 animate-spin"
style={{ borderColor: 'var(--border)', borderTopColor: 'var(--cyan)' }}
/>
</div>
);
}
export default function Orders() {
const [orders, setOrders] = useState([]);
const [fills, setFills] = useState([]);
const [loading, setLoading] = useState(true);
const [tab, setTab] = useState('orders');
useEffect(() => { load(); }, []);
async function load() {
setLoading(true);
try {
const [o, f] = await Promise.all([
post('/kalshi', { action: 'get_orders' }),
post('/kalshi', { action: 'get_fills' }),
]);
setOrders(o.orders || []);
setFills(f.fills || []);
} catch {}
setLoading(false);
}
async function cancelOrder(orderId) {
try {
await post('/kalshi', { action: 'cancel_order', order_id: orderId });
load();
} catch {}
}
if (loading) return <Spinner />;
return (
<div className="space-y-6 fade-in">
{/* ── Page Header ── */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{/* Arrows icon */}
<div
className="flex items-center justify-center w-10 h-10 rounded-xl"
style={{ background: 'var(--cyan-dim)', color: 'var(--cyan)' }}
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M7 16V4m0 0L3 8m4-4l4 4" />
<path d="M17 8v12m0 0l4-4m-4 4l-4-4" />
</svg>
</div>
<div>
<h1 className="heading text-3xl" style={{ color: 'var(--text)' }}>Orders</h1>
<p className="section-subtitle mt-0.5">
{orders.length} orders • {fills.length} fills
</p>
</div>
</div>
<button onClick={load} className="btn btn-o btn-sm">Refresh</button>
</div>
{/* ── Tab Switcher ── */}
<div
className="flex rounded-xl p-1 gap-1 w-fit"
style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
>
<button
onClick={() => setTab('orders')}
className="btn btn-sm"
style={
tab === 'orders'
? { background: 'var(--cyan)', color: 'var(--bg)' }
: { background: 'transparent', color: 'var(--text-muted)', border: 'none' }
}
>
Orders
<span
className="badge ml-1"
style={
tab === 'orders'
? { background: 'rgba(0,0,0,0.2)', color: 'var(--bg)', fontSize: '0.65rem' }
: { background: 'var(--surface)', color: 'var(--text-muted)', fontSize: '0.65rem' }
}
>
{orders.length}
</span>
</button>
<button
onClick={() => setTab('fills')}
className="btn btn-sm"
style={
tab === 'fills'
? { background: 'var(--cyan)', color: 'var(--bg)' }
: { background: 'transparent', color: 'var(--text-muted)', border: 'none' }
}
>
Fills
<span
className="badge ml-1"
style={
tab === 'fills'
? { background: 'rgba(0,0,0,0.2)', color: 'var(--bg)', fontSize: '0.65rem' }
: { background: 'var(--surface)', color: 'var(--text-muted)', fontSize: '0.65rem' }
}
>
{fills.length}
</span>
</button>
</div>
{/* ── Orders Table ── */}
{tab === 'orders' && (
<div className="card overflow-x-auto" style={{ padding: 0 }}>
<table>
<thead>
<tr>
<th>Ticker</th>
<th>Side</th>
<th>Action</th>
<th>Type</th>
<th>Price</th>
<th>Qty</th>
<th>Filled</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{orders.length === 0 ? (
<EmptyState label="No orders yet" />
) : (
orders.map((o, i) => (
<tr key={i}>
<td>
<a
href={`https://kalshi.com/search?query=${encodeURIComponent((o.ticker || '').split('-')[0])}`}
target="_blank"
rel="noopener noreferrer"
className="mono text-xs hover:underline transition"
style={{ color: 'var(--orange)' }}
>
{o.ticker}
</a>
</td>
<td>
<span className={`badge ${o.side === 'yes' ? 'bg-green' : 'bg-red'}`}>
{(o.side || '').toUpperCase()}
</span>
</td>
<td className="text-xs" style={{ color: 'var(--text-dim)' }}>
{(o.action || '').toUpperCase()}
</td>
<td className="text-xs" style={{ color: 'var(--text-muted)' }}>{o.type}</td>
<td className="mono" style={{ color: 'var(--cyan)' }}>
{o.yes_price || o.no_price || '—'}¢
</td>
<td className="mono">{o.count || o.remaining_count || 0}</td>
<td className="mono text-green">
{o.count - (o.remaining_count || 0)}
</td>
<td>
<span className={`badge ${statusBadge(o.status)}`}>{o.status}</span>
</td>
<td>
{o.status === 'resting' && (
<button
onClick={() => cancelOrder(o.order_id)}
className="btn btn-d btn-sm"
>
Cancel
</button>
)}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
)}
{/* ── Fills Table ── */}
{tab === 'fills' && (
<div className="card overflow-x-auto" style={{ padding: 0 }}>
<table>
<thead>
<tr>
<th>Ticker</th>
<th>Side</th>
<th>Action</th>
<th>Price</th>
<th>Qty</th>
<th>Maker/Taker</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{fills.length === 0 ? (
<EmptyState label="No fills yet" />
) : (
fills.map((f, i) => (
<tr key={i}>
<td>
<a
href={`https://kalshi.com/search?query=${encodeURIComponent((f.ticker || '').split('-')[0])}`}
target="_blank"
rel="noopener noreferrer"
className="mono text-xs hover:underline transition"
style={{ color: 'var(--orange)' }}
>
{f.ticker}
</a>
</td>
<td>
<span className={`badge ${f.side === 'yes' ? 'bg-green' : 'bg-red'}`}>
{(f.side || '').toUpperCase()}
</span>
</td>
<td className="text-xs" style={{ color: 'var(--text-dim)' }}>
{(f.action || '').toUpperCase()}
</td>
<td className="mono" style={{ color: 'var(--cyan)' }}>
{f.yes_price || f.no_price || '—'}¢
</td>
<td className="mono">{f.count}</td>
<td>
{f.is_taker ? (
<span className="badge bg-orange">TAKER</span>
) : (
<span className="badge bg-green">MAKER</span>
)}
</td>
<td className="mono text-xs" style={{ color: 'var(--text-muted)' }}>
{f.created_time ? new Date(f.created_time).toLocaleString() : '—'}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
)}
</div>
);
}