diff --git a/src/App.tsx b/src/App.tsx
index e00a344..2ac8252 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,29 +1,9 @@
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
-import { useState, useEffect } from 'react'
import Layout from './components/Layout'
+import { useStats } from './contexts/StatsContext'
import './App.css'
-interface ChatMessage {
- steamID64: string
- userName: string
- message: string
- timeStamp: number
-}
-
-interface TopPlayer {
- steamID64: string
- userName: string
- playTime: number
-}
-
-interface Stats {
- totalPlayers: number
- totalConnects: number
- totalPlayTime: number
- totalKills: number
-}
-
// Loading Skeleton Component
const LoadingSkeleton = ({ width = '60px', height = '40px', className = '' }: { width?: string, height?: string, className?: string }) => (
({
- totalPlayers: 0,
- totalConnects: 0,
- totalPlayTime: 0,
- totalKills: 0
- })
- const [loading, setLoading] = useState(true)
- const [recentChats, setRecentChats] = useState([])
- const [topPlayers, setTopPlayers] = useState([])
-
- // Fetch statistics data from API
- useEffect(() => {
- const timeRangeEnd = Date.now()
- const timeRangeStart = 0 // all time
- const requestBody = { timeRangeStart, timeRangeEnd }
-
- Promise.all([
- fetch('/api/server/statistics/total-player-count', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(requestBody)
- }),
- fetch('/api/server/statistics/total-connect-count', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(requestBody)
- }),
- fetch('/api/server/statistics/total-play-time', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(requestBody)
- }),
- fetch('/api/server/statistics/total-kill-count', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- ...requestBody,
- headshotOnly: false,
- weaponFilter: [],
- playerFilter: []
- })
- })
- ])
- .then(responses => Promise.all(responses.map(r => r.json())))
- .then(([players, connects, playTime, kills]) => {
- setStats({
- totalPlayers: players.count || 0,
- totalConnects: connects.totalConnectCount || 0,
- totalPlayTime: playTime.totalPlayTime || 0,
- totalKills: kills.totalKillCount || 0
- })
- setLoading(false)
- })
- .catch(error => {
- console.error('Error fetching stats:', error)
- setLoading(false)
- })
- }, [])
-
- // Fetch recent chat messages (limit to top 5)
- useEffect(() => {
- fetch('/api/server/statistics/recent-chat-message', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- timeRangeStart: Date.now() - 3600000, // last hour
- timeRangeEnd: Date.now()
- })
- })
- .then(r => r.json())
- .then(data => {
- // Limit to top 5 most recent messages
- const messages = data.messages || []
- const top5 = messages
- .sort((a: ChatMessage, b: ChatMessage) => b.timeStamp - a.timeStamp)
- .slice(0, 5)
- setRecentChats(top5)
- })
- .catch(error => console.error('Error fetching chat messages:', error))
- }, [])
-
- // Fetch top players by playtime
- useEffect(() => {
- fetch('/api/server/statistics/top-play-time', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- timeRangeStart: 0, // all time
- timeRangeEnd: Date.now()
- })
- })
- .then(r => r.json())
- .then(data => {
- // Get top 3 players
- const players = data.players || []
- setTopPlayers(players.slice(0, 3))
- })
- .catch(error => console.error('Error fetching top players:', error))
- }, [])
+ const { stats, recentChats, topPlayers, loading } = useStats()
// Format playtime from seconds to hours and minutes
const formatPlayTime = (seconds: number) => {
diff --git a/src/contexts/StatsContext.tsx b/src/contexts/StatsContext.tsx
new file mode 100644
index 0000000..3762e15
--- /dev/null
+++ b/src/contexts/StatsContext.tsx
@@ -0,0 +1,151 @@
+import { createContext, useContext, useState, useEffect } from 'react'
+import type { ReactNode } from 'react'
+
+interface ChatMessage {
+ steamID64: string
+ userName: string
+ message: string
+ timeStamp: number
+}
+
+interface TopPlayer {
+ steamID64: string
+ userName: string
+ playTime: number
+}
+
+interface Stats {
+ totalPlayers: number
+ totalConnects: number
+ totalPlayTime: number
+ totalKills: number
+}
+
+interface StatsContextType {
+ stats: Stats
+ recentChats: ChatMessage[]
+ topPlayers: TopPlayer[]
+ loading: boolean
+ lastUpdated: number
+}
+
+const StatsContext = createContext(undefined)
+
+export function StatsProvider({ children }: { children: ReactNode }) {
+ const [stats, setStats] = useState({
+ totalPlayers: 0,
+ totalConnects: 0,
+ totalPlayTime: 0,
+ totalKills: 0
+ })
+ const [recentChats, setRecentChats] = useState([])
+ const [topPlayers, setTopPlayers] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [lastUpdated, setLastUpdated] = useState(Date.now())
+
+ useEffect(() => {
+ const fetchAllData = async () => {
+ const timeRangeEnd = Date.now()
+ const timeRangeStart = 0 // all time
+ const requestBody = { timeRangeStart, timeRangeEnd }
+
+ try {
+ // Fetch statistics
+ const [playersRes, connectsRes, playTimeRes, killsRes, chatsRes, topPlayersRes] = await Promise.all([
+ fetch('/api/server/statistics/total-player-count', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(requestBody)
+ }),
+ fetch('/api/server/statistics/total-connect-count', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(requestBody)
+ }),
+ fetch('/api/server/statistics/total-play-time', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(requestBody)
+ }),
+ fetch('/api/server/statistics/total-kill-count', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ ...requestBody,
+ headshotOnly: false,
+ weaponFilter: [],
+ playerFilter: []
+ })
+ }),
+ fetch('/api/server/statistics/recent-chat-message', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ timeRangeStart: Date.now() - 3600000, // last hour
+ timeRangeEnd: Date.now()
+ })
+ }),
+ fetch('/api/server/statistics/top-play-time', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ timeRangeStart: 0, // all time
+ timeRangeEnd: Date.now()
+ })
+ })
+ ])
+
+ const [players, connects, playTime, kills, chats, topPlayersData] = await Promise.all([
+ playersRes.json(),
+ connectsRes.json(),
+ playTimeRes.json(),
+ killsRes.json(),
+ chatsRes.json(),
+ topPlayersRes.json()
+ ])
+
+ // Update statistics
+ setStats({
+ totalPlayers: players.count || 0,
+ totalConnects: connects.totalConnectCount || 0,
+ totalPlayTime: playTime.totalPlayTime || 0,
+ totalKills: kills.totalKillCount || 0
+ })
+
+ // Update recent chats (limit to top 5)
+ const messages = chats.messages || []
+ const top5 = messages
+ .sort((a: ChatMessage, b: ChatMessage) => b.timeStamp - a.timeStamp)
+ .slice(0, 5)
+ setRecentChats(top5)
+
+ // Update top players (limit to top 3)
+ const playersArray = topPlayersData.players || []
+ setTopPlayers(playersArray.slice(0, 3))
+
+ setLoading(false)
+ setLastUpdated(Date.now())
+ } catch (error) {
+ console.error('Error fetching stats:', error)
+ setLoading(false)
+ }
+ }
+
+ // Fetch data only once on mount
+ fetchAllData()
+ }, []) // Empty dependency array - runs only once
+
+ return (
+
+ {children}
+
+ )
+}
+
+export function useStats() {
+ const context = useContext(StatsContext)
+ if (context === undefined) {
+ throw new Error('useStats must be used within a StatsProvider')
+ }
+ return context
+}
diff --git a/src/main.tsx b/src/main.tsx
index 63b69e4..54da5fe 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -5,6 +5,7 @@ import './index.css'
import './i18n'
import { ThemeProvider } from './contexts/ThemeContext'
import { ServerProvider } from './contexts/ServerContext'
+import { StatsProvider } from './contexts/StatsContext'
import ScrollToTop from './components/ScrollToTop'
import App from './App.tsx'
import Friends from './pages/Friends.tsx'
@@ -15,18 +16,20 @@ import Forum from './pages/Forum.tsx'
createRoot(document.getElementById('root')!).render(
-
-
-
-
- } />
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
,
)