โ† Back
โ˜†
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...