import { useState, useEffect, useCallback } from 'react';
// --- Types ---
export interface UserAnalyticsTotals {
totalUsers: number;
onboarded: number; // users with a deposit wallet set up
activeCopiers: number; // distinct users with ≥1 active subscription
activeSubs: number;
pausedSubs: number;
openPositions: number;
closedPositions: number;
totalNotional: number; // sum of cost ($ entered) across copy positions
realizedPnl: number; // banked P&L of closed copy positions
totalDeposits: number; // sum of on-chain deposit-wallet balances (pUSD+USDC.e)
totalPositionsValue: number; // sum of live market value of open copies
totalBuilderFees: number; // real builder fees across ALL makers (CLOB /builder/trades)
}
export interface PopularLeader {
address: string;
subscribers: number;
label: string | null;
pseudonym: string | null;
winRate: number | null;
pnl: number | null;
volume: number | null;
trades: number | null;
category: string | null;
lastSeen: string | null;
}
export interface UserRow {
address: string;
createdAt: string | null;
lastLogin: string | null;
depositWallet: string | null;
activeSubs: number;
openPositions: number;
closedPositions: number;
notional: number;
realizedPnl: number;
positionsValue: number; // live market value of this user's open copies
unrealizedPnl: number; // positionsValue − open-position cost basis
depositBalance: number | null; // on-chain DW stablecoin balance (null until fetched)
country: string | null;
email: string | null;
builderFees: number; // REAL builder fees this user generated (CLOB /builder/trades, by maker=DW)
builderTrades: number; // count of fee-bearing builder trades for this user
}
const EMPTY_TOTALS: UserAnalyticsTotals = {
totalUsers: 0, onboarded: 0, activeCopiers: 0, activeSubs: 0, pausedSubs: 0,
openPositions: 0, closedPositions: 0, totalNotional: 0, realizedPnl: 0,
totalDeposits: 0, totalPositionsValue: 0, totalBuilderFees: 0,
};
// --- Hook ---
export function useUserAnalytics() {
const [totals, setTotals] = useState<UserAnalyticsTotals>(EMPTY_TOTALS);
const [leaders, setLeaders] = useState<PopularLeader[]>([]);
const [users, setUsers] = useState<UserRow[]>([]);
const [loading, setLoading] = useState(true);
const refresh = useCallback(async () => {
setLoading(true);
try {
const res = await fetch('/api/analytics/users');
const d = await res.json();
if (d.success && d.data) {
setTotals({ ...EMPTY_TOTALS, ...d.data.totals });
setLeaders(d.data.leaders ?? []);
setUsers(d.data.users ?? []);
}
} catch { /* ignore */ }
setLoading(false);
}, []);
useEffect(() => {
const t = setTimeout(refresh, 0);
const interval = setInterval(refresh, 5 * 60_000); // 5 min auto-refresh
return () => { clearTimeout(t); clearInterval(interval); };
}, [refresh]);
return { totals, leaders, users, loading, refresh };
}
📜 Git History
6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...