Files
www.cialloo.com/src/App.tsx

294 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { useState, useEffect } from 'react'
import Layout from './components/Layout'
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
}
function App() {
const { t } = useTranslation()
const [stats, setStats] = useState<Stats>({
totalPlayers: 0,
totalConnects: 0,
totalPlayTime: 0,
totalKills: 0
})
const [recentChats, setRecentChats] = useState<ChatMessage[]>([])
const [topPlayers, setTopPlayers] = useState<TopPlayer[]>([])
// 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
})
})
.catch(error => console.error('Error fetching stats:', error))
}, [])
// 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))
}, [])
// Format playtime from seconds to hours and minutes
const formatPlayTime = (seconds: number) => {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
if (hours > 0) {
if (minutes > 0) {
return `${hours}${t('time.hours')} ${minutes}${t('time.minutes')}`
}
return `${hours}${t('time.hours')}`
}
return `${minutes}${t('time.minutes')}`
}
// Format time ago from timestamp
const formatTimeAgo = (timestamp: number) => {
const seconds = Math.floor((Date.now() - timestamp) / 1000)
if (seconds < 60) return `${seconds}s ago`
const minutes = Math.floor(seconds / 60)
if (minutes < 60) return `${minutes}m ago`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `${hours}h ago`
const days = Math.floor(hours / 24)
return `${days}d ago`
}
// Format playtime to hours for leaderboard
const formatPlayTimeHours = (seconds: number) => {
return Math.floor(seconds / 3600)
}
return (
<Layout currentPage="home">
{/* Hero Section */}
<section className="hero">
<div className="hero-content">
<h1 className="hero-title">
{t('hero.title')}<br />
<span className="highlight">{t('hero.titleHighlight')}</span> {t('hero.titleEnd')}
</h1>
<p className="hero-subtitle">
{t('hero.subtitle')}
</p>
<div className="hero-buttons">
<button className="btn-primary">{t('hero.startPlaying')}</button>
<button className="btn-secondary">{t('hero.viewStats')}</button>
</div>
</div>
<div className="hero-visual">
<div className="game-preview">
<div className="preview-screen">
<div className="screen-content">
<div className="crosshair"></div>
<div className="health-bar">100 HP</div>
<div className="ammo-counter">30/90</div>
</div>
</div>
</div>
</div>
</section>
{/* Statistics Section */}
<section className="stats-section">
<div className="stats-container">
<div className="stat-card">
<div className="stat-icon">👥</div>
<div className="stat-number">{stats.totalPlayers.toLocaleString()}</div>
<div className="stat-label">{t('stats.totalPlayers')}</div>
</div>
<div className="stat-card">
<div className="stat-icon"><EFBFBD></div>
<div className="stat-number">{stats.totalConnects.toLocaleString()}</div>
<div className="stat-label">{t('stats.totalConnects')}</div>
</div>
<div className="stat-card">
<div className="stat-icon"></div>
<div className="stat-number">{formatPlayTime(stats.totalPlayTime)}</div>
<div className="stat-label">{t('stats.totalPlayTime')}</div>
</div>
<div className="stat-card">
<div className="stat-icon">🎯</div>
<div className="stat-number">{stats.totalKills.toLocaleString()}</div>
<div className="stat-label">{t('stats.totalKills')}</div>
</div>
</div>
</section>
{/* Features Section */}
<section className="features-section">
<div className="features-container">
<h2 className="section-title">{t('features.title')}</h2>
<div className="features-grid">
<div className="feature-card" id="servers">
<div className="feature-icon">🖥</div>
<h3>{t('features.serverBrowser.title')}</h3>
<p>{t('features.serverBrowser.description')}</p>
<Link to="/servers" className="feature-btn" style={{display: 'inline-block', textDecoration: 'none'}}>{t('features.browseServers')}</Link>
</div>
<div className="feature-card" id="blog">
<div className="feature-icon">📝</div>
<h3>{t('features.blog.title')}</h3>
<p>{t('features.blog.description')}</p>
<Link to="/blog" className="feature-btn" style={{display: 'inline-block', textDecoration: 'none'}}>{t('features.readBlog')}</Link>
</div>
<div className="feature-card" id="git">
<div className="feature-icon">📦</div>
<h3>{t('features.git.title')}</h3>
<p>{t('features.git.description')}</p>
<a href="https://git.cialloo.com" target="_blank" rel="noopener noreferrer" className="feature-btn" style={{display: 'inline-block', textDecoration: 'none'}}>{t('features.viewGitHub')}</a>
</div>
<div className="feature-card" id="forum">
<div className="feature-icon">💬</div>
<h3>{t('features.forum.title')}</h3>
<p>{t('features.forum.description')}</p>
<Link to="/forum" className="feature-btn" style={{display: 'inline-block', textDecoration: 'none'}}>{t('features.joinForum')}</Link>
</div>
<div className="feature-card" id="friends">
<div className="feature-icon">🔗</div>
<h3>{t('features.friends.title')}</h3>
<p>{t('features.friends.description')}</p>
<Link to="/friends" className="feature-btn" style={{display: 'inline-block', textDecoration: 'none'}}>{t('features.viewFriends')}</Link>
</div>
</div>
</div>
</section>
{/* Recent Activity Section */}
<section className="activity-section">
<div className="activity-container">
<div className="activity-main">
<h2 className="section-title">{t('activity.title')}</h2>
<div className="chat-feed">
{recentChats.map((chat, index) => (
<div key={index} className="chat-message">
<div className="chat-user">{chat.userName}:</div>
<div className="chat-text">{chat.message}</div>
<div className="chat-time">{formatTimeAgo(chat.timeStamp)}</div>
</div>
))}
</div>
</div>
<div className="activity-sidebar">
<div className="sidebar-card">
<h3>{t('activity.topPlayers')}</h3>
<div className="leaderboard">
{topPlayers.map((player, index) => (
<div key={player.steamID64} className="leader-item">
{index + 1}. {player.userName} - {formatPlayTimeHours(player.playTime)}h
</div>
))}
</div>
</div>
<div className="sidebar-card">
<h3>{t('activity.serverInfo')}</h3>
<div className="quick-stats">
<div>{t('activity.serverStatus')}</div>
<div>{t('activity.currentMap')}</div>
<div>{t('activity.nextRestart')}</div>
</div>
</div>
</div>
</div>
</section>
</Layout>
)
}
export default App