import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import SubTabs from '../components/shared/SubTabs';
import WhaleFeed from '../components/signals/WhaleFeed';
import { useTopTraders } from '../hooks/useTopTraders';
import { useWhaleConsensus } from '../hooks/useWhaleConsensus';
import { useWhaleAlerts } from '../hooks/useWhaleAlerts';
import { formatVolume } from '../utils/format';
import { useT } from '../i18n/LanguageContext';
type WhaleTab = 'trades' | 'consensus' | 'alerts' | 'leaders';
function fmtAddr(addr: string): string {
return `${addr.slice(0, 6)}โฆ${addr.slice(-4)}`;
}
function Consensus() {
const navigate = useNavigate();
const { t } = useT();
const { rows, loading } = useWhaleConsensus({ days: 7, minAmount: 5000, limit: 40 });
if (loading && rows.length === 0) {
return (
<div className="wf-skeleton">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="wf-skeleton-row" />
))}
</div>
);
}
if (rows.length === 0) {
return (
<div className="wf-empty">
<div className="wf-empty-icon">๐ค</div>
<div className="wf-empty-title">{t('whales.noConsensus')}</div>
<div className="wf-empty-sub">{t('whales.noConsensusSub')}</div>
</div>
);
}
return (
<div className="wcs-list">
{rows.map(r => {
const yes = r.netSide === 'Yes';
return (
<div
key={r.marketId}
className={`wcs-card ${yes ? 'wf-dir-yes' : 'wf-dir-no'}`}
onClick={() => navigate(`/market/${r.marketId}`)}
>
<div className="wcs-head">
<span className={`wf-bet ${yes ? 'wf-bet-yes' : 'wf-bet-no'}`}>
๐ {r.whales} {t(r.whales === 1 ? 'whales.whaleOne' : 'whales.whaleMany')} โ {r.netSide}
</span>
<span className="wcs-flow">{formatVolume(r.netFlowUsd)}</span>
</div>
<div className="wcs-q">{r.question || 'Unknown market'}</div>
<div className="wcs-meta">
<span>{t('whales.agreement', { p: Math.round(r.agreement * 100) })}</span>
<span>ยท</span>
<span>{t('whales.avgEntry', { c: Math.round(r.avgPrice * 100) })}</span>
<span>ยท</span>
<span>{r.trades} {t(r.trades === 1 ? 'whales.tradeOne' : 'whales.tradeMany')}</span>
</div>
</div>
);
})}
</div>
);
}
function Leaderboard() {
const navigate = useNavigate();
const { t: tr } = useT(); // aliased: the traders.map() below uses `t` as the item
const { traders, loading } = useTopTraders('volume', 30);
if (loading && traders.length === 0) {
return (
<div className="wf-skeleton">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="wf-skeleton-row" />
))}
</div>
);
}
if (traders.length === 0) {
return (
<div className="wf-empty">
<div className="wf-empty-icon">๐</div>
<div className="wf-empty-title">{tr('whales.noWallets')}</div>
<div className="wf-empty-sub">{tr('whales.noWalletsSub')}</div>
</div>
);
}
return (
<div className="whl-list">
{traders.map((t, i) => {
const wr = t.winRate != null ? `${Math.round(t.winRate * 100)}%` : 'โ';
const wrCls = t.winRate == null ? '' : t.winRate >= 0.6 ? 'whl-wr-good' : t.winRate <= 0.4 ? 'whl-wr-bad' : '';
return (
<div key={t.address} className="whl-row" onClick={() => navigate('/whale', { state: { address: t.address } })}>
<span className="whl-rank">{i + 1}</span>
<div className="whl-info">
<span className="whl-label">{t.label || fmtAddr(t.address)}</span>
<span className="whl-meta">
{formatVolume(t.totalVolume)} ยท {t.totalTrades} {tr('whales.tradeMany')}
{t.topCategory && ` ยท ${t.topCategory}`}
</span>
</div>
<span className={`whl-wr ${wrCls}`}>{wr}</span>
</div>
);
})}
</div>
);
}
function WhaleAlertsList() {
const navigate = useNavigate();
const { t } = useT();
const { alerts, loading, unsubscribe, markSeen } = useWhaleAlerts();
if (loading && alerts.length === 0) {
return (
<div className="wf-skeleton">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="wf-skeleton-row" />
))}
</div>
);
}
if (alerts.length === 0) {
return (
<div className="wf-empty">
<div className="wf-empty-icon">๐</div>
<div className="wf-empty-title">{t('whales.noSubs')}</div>
<div className="wf-empty-sub">{t('whales.noSubsSub')}</div>
</div>
);
}
const open = (address: string, pending: number) => {
if (pending > 0) markSeen(address);
navigate('/whale', { state: { address } });
};
return (
<div className="whl-list">
{alerts.map(a => {
const wr = a.winRate != null ? `${Math.round(a.winRate * 100)}%` : 'โ';
return (
<div key={a.id} className="whl-row" onClick={() => open(a.whaleAddress, a.pendingCount)}>
<div className="whl-info">
<span className="whl-label">
{a.label || fmtAddr(a.whaleAddress)}
{a.pendingCount > 0 && (
<span className="wal-badge">{t('whales.newCount', { n: a.pendingCount })}</span>
)}
</span>
<span className="whl-meta">
{formatVolume(a.totalVolume ?? 0)} ยท {a.totalTrades ?? 0} {t('whales.tradeMany')} ยท WR {wr}
</span>
</div>
<button
className="wal-unsub"
onClick={(e) => { e.stopPropagation(); unsubscribe(a.whaleAddress); }}
title={t('whales.unsub')}
>
โ
</button>
</div>
);
})}
</div>
);
}
export default function WhalesPage() {
const { t } = useT();
const [tab, setTab] = useState<WhaleTab>('trades');
const tabs = [
{ key: 'trades', label: t('whales.tabTrades') },
{ key: 'consensus', label: t('whales.tabConsensus') },
{ key: 'alerts', label: t('whales.tabAlerts') },
{ key: 'leaders', label: t('whales.tabLeaders') },
];
return (
<div className="whales-page">
<h2 className="sig-title">{t('whales.title')}</h2>
<p className="sig-desc">{t('whales.desc')}</p>
<SubTabs tabs={tabs} active={tab} onChange={k => setTab(k as WhaleTab)} />
{tab === 'trades' && <WhaleFeed limit={50} minAmount={5000} days={7} />}
{tab === 'consensus' && <Consensus />}
{tab === 'alerts' && <WhaleAlertsList />}
{tab === 'leaders' && <Leaderboard />}
</div>
);
}
๐ Git History
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...