144 lines
4.2 KiB
TypeScript
144 lines
4.2 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
|
|
interface DashboardStats {
|
|
bot: {
|
|
name: string;
|
|
avatarUrl: string | null;
|
|
};
|
|
guilds: {
|
|
count: number;
|
|
};
|
|
users: {
|
|
active: number;
|
|
total: number;
|
|
};
|
|
commands: {
|
|
total: number;
|
|
};
|
|
ping: {
|
|
avg: number;
|
|
};
|
|
economy: {
|
|
totalWealth: string;
|
|
avgLevel: number;
|
|
topStreak: number;
|
|
totalItems: number;
|
|
};
|
|
activeLootdrops: Array<{
|
|
rewardAmount: number;
|
|
currency: string;
|
|
createdAt: string;
|
|
expiresAt: string | null;
|
|
}>;
|
|
leaderboards: {
|
|
topLevels: Array<{ username: string; level: number | null }>;
|
|
topWealth: Array<{ username: string; balance: string }>;
|
|
};
|
|
recentEvents: Array<{
|
|
type: 'success' | 'error' | 'info' | 'warn';
|
|
message: string;
|
|
timestamp: string;
|
|
icon?: string;
|
|
}>;
|
|
uptime: number;
|
|
lastCommandTimestamp: number | null;
|
|
maintenanceMode: boolean;
|
|
}
|
|
|
|
interface UseDashboardStatsResult {
|
|
stats: DashboardStats | null;
|
|
loading: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
/**
|
|
* Custom hook to fetch and auto-refresh dashboard statistics using WebSockets with HTTP fallback
|
|
*/
|
|
export function useDashboardStats(): UseDashboardStatsResult {
|
|
const [stats, setStats] = useState<DashboardStats | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchStats = async () => {
|
|
try {
|
|
const response = await fetch("/api/stats");
|
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
const data = await response.json();
|
|
setStats(data);
|
|
setError(null);
|
|
} catch (err) {
|
|
console.error("Failed to fetch dashboard stats:", err);
|
|
setError(err instanceof Error ? err.message : "Failed to fetch stats");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
// Initial fetch
|
|
fetchStats();
|
|
|
|
// WebSocket setup
|
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
const wsUrl = `${protocol}//${window.location.host}/ws`;
|
|
let socket: WebSocket | null = null;
|
|
let reconnectTimeout: Timer | null = null;
|
|
|
|
const connect = () => {
|
|
socket = new WebSocket(wsUrl);
|
|
|
|
socket.onopen = () => {
|
|
console.log("🟢 [WS] Connected to dashboard live stream");
|
|
setError(null);
|
|
if (reconnectTimeout) {
|
|
clearTimeout(reconnectTimeout);
|
|
reconnectTimeout = null;
|
|
}
|
|
};
|
|
|
|
socket.onmessage = (event) => {
|
|
try {
|
|
const message = JSON.parse(event.data);
|
|
|
|
if (message.type === "STATS_UPDATE") {
|
|
setStats(message.data);
|
|
} else if (message.type === "NEW_EVENT") {
|
|
setStats(prev => {
|
|
if (!prev) return prev;
|
|
return {
|
|
...prev,
|
|
recentEvents: [message.data, ...prev.recentEvents].slice(0, 10)
|
|
};
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error("Error parsing WS message:", e);
|
|
}
|
|
};
|
|
|
|
socket.onclose = () => {
|
|
console.log("🟠 [WS] Connection lost. Attempting reconnect in 5s...");
|
|
reconnectTimeout = setTimeout(connect, 5000);
|
|
};
|
|
|
|
socket.onerror = (err) => {
|
|
console.error("🔴 [WS] Socket error:", err);
|
|
socket?.close();
|
|
};
|
|
};
|
|
|
|
connect();
|
|
|
|
// Cleanup on unmount
|
|
return () => {
|
|
if (socket) {
|
|
socket.onclose = null; // Prevent reconnect on intentional close
|
|
socket.close();
|
|
}
|
|
if (reconnectTimeout) clearTimeout(reconnectTimeout);
|
|
};
|
|
}, []);
|
|
|
|
return { stats, loading, error };
|
|
}
|