import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import type { ScreenerEvent, EventOutcome } from '../../hooks/useEvents';
import { formatVolume, formatPrice, timeUntil } from '../../utils/format';
import { CategoryBadge } from './MarketsTable';
import { useT } from '../../i18n/LanguageContext';
interface Props {
events: ScreenerEvent[];
loading: boolean;
}
export default function EventsList({ events, loading }: Props) {
const { t } = useT();
if (loading && events.length === 0) {
return (
<div className="mc-grid">
{Array.from({ length: 6 }, (_, i) => (
<div key={i} className="mc-card mc-skeleton">
<div className="mc-head">
<div className="skeleton-img" />
<div className="skeleton-text" style={{ flex: 1, height: 14 }} />
</div>
<div className="skeleton-text" style={{ height: 40, marginTop: 10 }} />
</div>
))}
</div>
);
}
if (events.length === 0) {
return (
<div className="table-container">
<div className="table-empty">
<p>{t('mkt.noEvents')}</p>
<span>{t('mkt.noEventsSub')}</span>
</div>
</div>
);
}
return (
<div className="mc-grid">
{events.map(ev => (
<EventCard key={ev.eventId} ev={ev} />
))}
</div>
);
}
function EventCard({ ev }: { ev: ScreenerEvent }) {
const { t } = useT();
const navigate = useNavigate();
const endsIn = timeUntil(ev.endDate);
const tops = ev.topOutcomes ?? [];
const more = (ev.outcomes ?? tops.length) - tops.length;
const [modalOpen, setModalOpen] = useState(false);
const [allOutcomes, setAllOutcomes] = useState<EventOutcome[] | null>(null);
const [loadingAll, setLoadingAll] = useState(false);
const openAll = async () => {
setModalOpen(true);
if (allOutcomes || loadingAll) return;
setLoadingAll(true);
try {
const r = await fetch(`/api/screener/events/${ev.eventId}/outcomes`);
const d = await r.json();
if (d.success) setAllOutcomes(d.data?.outcomes ?? []);
} catch { /* ignore */ }
finally { setLoadingAll(false); }
};
const goMarket = (marketId: string) => { setModalOpen(false); navigate(`/market/${marketId}`); };
return (
<div className="ev-card">
<div className="ev-head">
{ev.imageUrl && (
<img src={ev.imageUrl} alt="" className="mc-img" loading="lazy"
onError={e => { (e.target as HTMLImageElement).style.display = 'none'; }} />
)}
<span className="mc-title">{ev.title}</span>
<span className="ev-count">{ev.outcomes} {t(ev.outcomes === 1 ? 'mkt.outcomeOne' : 'mkt.outcomeMany')}</span>
</div>
<div className="mc-meta-row">
<CategoryBadge category={ev.category} />
<span className="mc-stat">{t('mkt.volShort')} {formatVolume(ev.volume24h)}</span>
<span className="mc-stat">{t('mkt.liqShort')} {formatVolume(ev.liquidity)}</span>
{endsIn && <span className="mc-stat mc-stat-ends">⏳ {endsIn}</span>}
</div>
<div className="ev-outcomes">
{tops.map(o => {
const pct = Math.round(Math.max(0, Math.min(100, o.yesPrice * 100)));
const color = pct >= 70 ? 'var(--profit)' : pct <= 30 ? 'var(--loss)' : 'var(--text-primary)';
return (
<div
key={o.marketId}
className="ev-out"
onClick={() => navigate(`/market/${o.marketId}`)}
>
<span className="ev-out-label">{o.label || o.question}</span>
<span className="ev-out-price" style={{ color }}>{formatPrice(o.yesPrice)}</span>
</div>
);
})}
{more > 0 && (
<button
type="button"
className="ev-more"
onClick={e => { e.stopPropagation(); openAll(); }}
>
+{more} {t(more === 1 ? 'mkt.outcomeOne' : 'mkt.outcomeMany')} →
</button>
)}
</div>
{modalOpen && (
<div
className="ev-modal-backdrop"
onClick={() => setModalOpen(false)}
style={{
position: 'fixed', inset: 0, zIndex: 1000,
background: 'rgba(0,0,0,0.55)', display: 'flex',
alignItems: 'center', justifyContent: 'center', padding: 16,
}}
>
<div
className="ev-modal"
onClick={e => e.stopPropagation()}
style={{
background: 'var(--card-bg)', borderRadius: 12, width: '100%',
maxWidth: 460, maxHeight: '80vh', overflowY: 'auto',
border: '1px solid var(--border)', padding: 16,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
<span className="mc-title" style={{ flex: 1 }}>{ev.title}</span>
<button
type="button"
onClick={() => setModalOpen(false)}
style={{ background: 'none', border: 'none', color: 'var(--text-secondary)', fontSize: 20, cursor: 'pointer', lineHeight: 1 }}
>×</button>
</div>
{loadingAll && <div className="ev-out-label" style={{ padding: 8 }}>…</div>}
{!loadingAll && (allOutcomes ?? []).map(o => {
const pct = Math.round(Math.max(0, Math.min(100, o.yesPrice * 100)));
const color = pct >= 70 ? 'var(--profit)' : pct <= 30 ? 'var(--loss)' : 'var(--text-primary)';
return (
<div key={o.marketId} className="ev-out" onClick={() => goMarket(o.marketId)}>
<span className="ev-out-label">{o.label || o.question}</span>
<span className="ev-out-price" style={{ color }}>{formatPrice(o.yesPrice)}</span>
</div>
);
})}
</div>
</div>
)}
</div>
);
}
📜 Git History
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...