import { useNavigate } from 'react-router-dom';
import type { TradeActivity } from '../../hooks/usePortfolioHistory';
import { formatUsd } from '../../utils/format';
import { useT } from '../../i18n/LanguageContext';
interface Props {
trades: TradeActivity[];
loading: boolean;
hasMore: boolean;
onLoadMore: () => void;
directions?: Record<string, string>;
}
export default function TradeHistory({ trades, loading, hasMore, onLoadMore, directions }: Props) {
const navigate = useNavigate();
const { t: tr } = useT();
if (!loading && trades.length === 0) {
return (
<div className="th-empty">
<p>{tr('th.empty')}</p>
<p className="th-empty-sub">{tr('th.emptySub')}</p>
</div>
);
}
return (
<div className="th-wrap">
<div className="th-list">
{trades.map(t => (
<div
key={t.id}
className="th-row"
onClick={() => t.conditionId && navigate(`/market/${t.conditionId}`)}
>
<div className="th-left">
{t.icon && (
<img src={t.icon} alt="" className="th-icon"
onError={e => { (e.target as HTMLImageElement).style.display = 'none'; }} />
)}
<div className="th-info">
<span className="th-title">{t.title || tr('th.unknownMarket')}
{directions?.[(t.conditionId || '').toLowerCase()] === 'fade' && (
<span style={{ color: '#ff7a7a', fontWeight: 700, fontSize: '0.7rem', marginLeft: 6 }}>๐ FADE</span>
)}
</span>
<div className="th-meta">
{t.type === 'REDEEM' ? (
// Redemptions have no side/price โ label them clearly (won โ payout >0,
// lost โ $0) instead of rendering an empty badge + "0.00 shares @ 0ยข".
<span className={`th-side ${t.amount > 0 ? 'th-redeem-win' : 'th-redeem-loss'}`}>
{tr('th.redeem')} ยท {t.amount > 0 ? tr('th.win') : tr('th.loss')}
</span>
) : (
<>
<span className={`th-side th-side-${t.side?.toLowerCase()}`}>{t.side}</span>
<span className={`th-outcome th-outcome-${t.outcome?.toLowerCase()}`}>{t.outcome}</span>
<span className="th-size">{t.size?.toFixed(2)} {tr('th.shares')}</span>
<span className="th-price">@ {(t.price * 100).toFixed(0)}ยข</span>
</>
)}
</div>
</div>
</div>
<div className="th-right">
{t.type === 'REDEEM' && t.realizedPnl < 0 ? (
// Losing redemption: Data API pays $0; show the amount actually lost
// (remaining cost basis) in red instead of a meaningless $0.
<span className="th-amount th-amt-loss">โ{formatUsd(Math.abs(t.realizedPnl))}</span>
) : t.type === 'REDEEM' ? (
// Winning redemption: payout in green, matching the win badge.
<span className="th-amount th-amt-win">{formatUsd(t.amount)}</span>
) : t.side === 'SELL' ? (
// Closed via sell: show realized P&L (proceeds โ cost), signed & colored.
<span className={`th-amount ${t.realizedPnl >= 0 ? 'th-amt-win' : 'th-amt-loss'}`}>
{t.realizedPnl >= 0 ? '+' : 'โ'}{formatUsd(Math.abs(t.realizedPnl))}
</span>
) : (
<span className="th-amount">{formatUsd(t.amount)}</span>
)}
<span className="th-time">{formatTime(t.createdAt, tr)}</span>
</div>
</div>
))}
</div>
{loading && (
<div className="th-loading">{tr('th.loading')}</div>
)}
{!loading && hasMore && (
<button className="th-load-more" onClick={onLoadMore}>{tr('th.loadMore')}</button>
)}
</div>
);
}
function formatTime(iso: string, tr: (k: string, p?: Record<string, string | number>) => string): string {
if (!iso) return '';
const d = new Date(iso);
const now = new Date();
const diff = now.getTime() - d.getTime();
const mins = Math.floor(diff / 60_000);
if (mins < 60) return tr('time.mAgo', { n: mins });
const hrs = Math.floor(mins / 60);
if (hrs < 24) return tr('time.hAgo', { n: hrs });
const days = Math.floor(hrs / 24);
if (days < 7) return tr('time.dAgo', { n: days });
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
}
๐ Git History
b9c19bfchore(poli): reconcile local Flow/Insider/manual-trade work with deployed state3 days ago
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...