feat: (ui) new design
This commit is contained in:
@@ -1,50 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export interface ActivityData {
|
||||
hour: string;
|
||||
commands: number;
|
||||
transactions: number;
|
||||
}
|
||||
|
||||
interface UseActivityStatsResult {
|
||||
data: ActivityData[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook to fetch hourly activity data for charts.
|
||||
* Data is cached on the server for 5 minutes.
|
||||
*/
|
||||
export function useActivityStats(): UseActivityStatsResult {
|
||||
const [data, setData] = useState<ActivityData[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchActivity = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch("/api/stats/activity");
|
||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||
const jsonData = await response.json();
|
||||
setData(jsonData);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch activity stats:", err);
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch activity");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchActivity();
|
||||
|
||||
// Refresh every 5 minutes to match server cache
|
||||
const interval = setInterval(fetchActivity, 5 * 60 * 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return { data, loading, error, refresh: fetchActivity };
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
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 };
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
}
|
||||
61
web/src/hooks/use-socket.ts
Normal file
61
web/src/hooks/use-socket.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import type { DashboardStats } from "@shared/modules/dashboard/dashboard.types";
|
||||
|
||||
export function useSocket() {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||
const socketRef = useRef<WebSocket | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Determine WS protocol based on current page schema
|
||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const host = window.location.host;
|
||||
const wsUrl = `${protocol}//${host}/ws`;
|
||||
|
||||
function connect() {
|
||||
const ws = new WebSocket(wsUrl);
|
||||
socketRef.current = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("Connected to dashboard websocket");
|
||||
setIsConnected(true);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const payload = JSON.parse(event.data);
|
||||
|
||||
if (payload.type === "STATS_UPDATE") {
|
||||
setStats(payload.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to parse WS message", err);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log("Disconnected from dashboard websocket");
|
||||
setIsConnected(false);
|
||||
// Simple reconnect logic
|
||||
setTimeout(connect, 3000);
|
||||
};
|
||||
|
||||
ws.onerror = (err) => {
|
||||
console.error("WebSocket error:", err);
|
||||
ws.close();
|
||||
};
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
if (socketRef.current) {
|
||||
// Prevent reconnect on unmount
|
||||
socketRef.current.onclose = null;
|
||||
socketRef.current.close();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { isConnected, stats };
|
||||
}
|
||||
Reference in New Issue
Block a user