Files
www.cialloo.com/src/pages/Servers.tsx
cialloo 4a0f9c1a67
All checks were successful
CI - Build and Push / Build and Push Docker Image (push) Successful in 13s
Remove maxPlayers from server data handling for simplified player count display
2025-10-06 19:27:05 +08:00

431 lines
15 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 { useState } from 'react'
import Layout from '../components/Layout'
import { useServers } from '../contexts/ServerContext'
import '../App.css'
// Loading Skeleton Component
const LoadingSkeleton = ({ width = '60px', height = '40px', className = '', style = {} }: { width?: string, height?: string, className?: string, style?: React.CSSProperties }) => (
<div
className={`loading-skeleton ${className}`}
style={{
width,
height,
background: 'linear-gradient(135deg, var(--bg-secondary) 0%, var(--border-color) 50%, var(--bg-secondary) 100%)',
backgroundSize: '200% 200%',
animation: 'loading-shimmer 2.5s infinite ease-in-out, loading-pulse 1.5s infinite ease-in-out',
borderRadius: '8px',
position: 'relative',
overflow: 'hidden',
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
...style
}}
>
<div
style={{
position: 'absolute',
top: 0,
left: '-100%',
width: '100%',
height: '100%',
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent)',
animation: 'loading-wave 2.5s infinite ease-in-out'
}}
/>
</div>
)
function Servers() {
const { t } = useTranslation()
const { servers, loading } = useServers()
const [selectedCategory, setSelectedCategory] = useState<string>('All')
// Filter servers by category
const filteredServers = selectedCategory === 'All'
? servers
: servers.filter(server => server.category === selectedCategory)
const categories = ['All', ...Array.from(new Set(servers.map(s => s.category)))]
const getStatusColor = (status: string) => {
switch (status) {
case 'online': return '#10b981'
case 'offline': return '#ef4444'
case 'loading': return '#f59e0b'
default: return '#6b7280'
}
}
return (
<Layout currentPage="servers">
{/* Servers Page Header */}
<section className="servers-header" style={{
padding: '120px 2rem 60px',
background: 'linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%)',
textAlign: 'center'
}}>
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
<h1 style={{
fontSize: '3rem',
fontWeight: 'bold',
marginBottom: '1rem',
background: 'linear-gradient(45deg, var(--accent-primary), var(--accent-secondary))',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
}}>
{t('servers.title')}
</h1>
<p style={{
fontSize: '1.2rem',
color: 'var(--text-secondary)',
marginBottom: '2rem'
}}>
{t('servers.subtitle')}
</p>
{/* Category Filter */}
<div style={{
display: 'flex',
justifyContent: 'center',
gap: '1rem',
marginBottom: '2rem',
flexWrap: 'wrap'
}}>
{categories.map(category => (
<button
key={category}
onClick={() => setSelectedCategory(category)}
style={{
padding: '0.5rem 1rem',
border: `2px solid ${selectedCategory === category ? 'var(--accent-primary)' : 'var(--border-color)'}`,
background: selectedCategory === category ? 'var(--accent-primary)' : 'var(--bg-secondary)',
color: selectedCategory === category ? 'white' : 'var(--text-primary)',
borderRadius: '20px',
cursor: 'pointer',
fontWeight: 'bold',
transition: 'all 0.3s ease'
}}
>
{category}
</button>
))}
</div>
</div>
</section>
{/* Servers Grid */}
<section className="servers-grid" style={{
padding: '2rem',
maxWidth: '1200px',
margin: '0 auto'
}}>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))',
gap: '2rem'
}}>
{loading ? (
// Show loading skeletons
servers.length > 0 ? servers.map((_, index) => (
<div
key={`loading-${index}`}
className="server-card"
style={{
background: 'var(--bg-secondary)',
border: '1px solid var(--border-color)',
borderRadius: '12px',
padding: '1.5rem',
position: 'relative',
overflow: 'hidden'
}}
>
{/* Status Indicator Skeleton */}
<div style={{
position: 'absolute',
top: '1rem',
right: '1rem',
width: '12px',
height: '12px',
borderRadius: '50%',
backgroundColor: 'var(--border-color)'
}} />
{/* Server Header Skeleton */}
<div style={{ marginBottom: '1rem' }}>
<LoadingSkeleton width="180px" height="24px" style={{ marginBottom: '0.5rem' }} />
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
<LoadingSkeleton width="60px" height="16px" />
<LoadingSkeleton width="40px" height="16px" />
</div>
</div>
{/* Server Stats Skeleton */}
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '1rem',
marginBottom: '1rem'
}}>
<div>
<LoadingSkeleton width="50px" height="14px" style={{ marginBottom: '0.25rem' }} />
<LoadingSkeleton width="40px" height="20px" />
</div>
<div>
<LoadingSkeleton width="50px" height="14px" style={{ marginBottom: '0.25rem' }} />
<LoadingSkeleton width="40px" height="20px" />
</div>
</div>
{/* Server Details Skeleton */}
<div style={{ marginBottom: '1rem' }}>
<LoadingSkeleton width="120px" height="16px" style={{ marginBottom: '0.5rem' }} />
</div>
{/* Join Button Skeleton */}
<LoadingSkeleton width="100%" height="40px" />
</div>
)) : []
) : (
filteredServers.map((server) => (
<div
key={`${server.ip}:${server.port}`}
className="server-card"
style={{
background: 'var(--bg-secondary)',
border: '1px solid var(--border-color)',
borderRadius: '12px',
padding: '1.5rem',
transition: 'all 0.3s ease',
cursor: 'pointer',
position: 'relative',
overflow: 'hidden'
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-5px)'
e.currentTarget.style.boxShadow = '0 10px 25px rgba(0,0,0,0.2)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)'
e.currentTarget.style.boxShadow = 'none'
}}
>
{/* Status Indicator */}
<div style={{
position: 'absolute',
top: '1rem',
right: '1rem',
width: '12px',
height: '12px',
borderRadius: '50%',
backgroundColor: getStatusColor(server.status)
}} />
{/* Server Header */}
<div style={{ marginBottom: '1rem' }}>
<h3 style={{
fontSize: '1.2rem',
fontWeight: 'bold',
color: 'var(--text-primary)',
marginBottom: '0.5rem'
}}>
{server.name}
</h3>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
fontSize: '0.9rem',
color: 'var(--text-secondary)'
}}>
<span>🗺 {server.mapName || 'Loading...'}</span>
<span></span>
<span>{server.category}</span>
</div>
</div>
{/* Server Stats */}
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '1rem',
marginBottom: '1rem'
}}>
<div>
<div style={{
fontSize: '0.8rem',
color: 'var(--text-secondary)',
marginBottom: '0.25rem'
}}>
{t('servers.players')}
</div>
<div style={{
fontSize: '1.1rem',
fontWeight: 'bold',
color: 'var(--text-primary)'
}}>
{server.status === 'online' ? server.playerCount : 'Loading...'}
</div>
</div>
<div>
<div style={{
fontSize: '0.8rem',
color: 'var(--text-secondary)',
marginBottom: '0.25rem'
}}>
{t('servers.status')}
</div>
<div style={{
fontSize: '1.1rem',
fontWeight: 'bold',
color: server.status === 'online' ? '#10b981' : server.status === 'offline' ? '#ef4444' : '#f59e0b'
}}>
{server.status === 'online' ? 'Online' : server.status === 'offline' ? 'Offline' : 'Loading'}
</div>
</div>
</div>
{/* Server Details */}
<div style={{ marginBottom: '1rem' }}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
fontSize: '0.9rem',
color: 'var(--text-secondary)',
marginBottom: '0.5rem'
}}>
<span><EFBFBD> {server.ip}:{server.port}</span>
<span style={{
background: 'var(--accent-primary)',
color: 'white',
padding: '0.25rem 0.5rem',
borderRadius: '4px',
fontSize: '0.8rem',
fontWeight: 'bold'
}}>
{server.category}
</span>
</div>
</div>
{/* Join Button */}
<button
style={{
width: '100%',
padding: '0.75rem',
background: 'transparent',
color: server.status === 'online' ? 'var(--accent-primary)' : 'var(--text-secondary)',
border: `2px solid ${server.status === 'online' ? 'var(--accent-primary)' : 'var(--text-secondary)'}`,
borderRadius: '6px',
fontWeight: 'bold',
cursor: server.status === 'online' ? 'pointer' : 'not-allowed',
transition: 'all 0.3s ease'
}}
disabled={server.status !== 'online'}
onClick={() => {
if (server.status === 'online') {
window.open(`steam://connect/${server.ip}:${server.port}`)
}
}}
>
{server.status === 'online' ? t('servers.joinServer') : t('servers.offline')}
</button>
</div>
))
)}
</div>
</section>
{/* Server Stats Summary */}
<section className="server-stats" style={{
padding: '4rem 2rem',
background: 'var(--bg-secondary)',
textAlign: 'center'
}}>
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
<h2 style={{
fontSize: '2rem',
fontWeight: 'bold',
marginBottom: '2rem',
color: 'var(--text-primary)'
}}>
{t('servers.serverStats')}
</h2>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: '2rem'
}}>
<div>
<div style={{
fontSize: '2.5rem',
fontWeight: 'bold',
color: 'var(--accent-primary)',
marginBottom: '0.5rem'
}}>
{servers.filter(s => s.status === 'online').length}
</div>
<div style={{
color: 'var(--text-secondary)',
fontSize: '1.1rem'
}}>
{t('servers.onlineServers')}
</div>
</div>
<div>
<div style={{
fontSize: '2.5rem',
fontWeight: 'bold',
color: 'var(--accent-primary)',
marginBottom: '0.5rem'
}}>
{servers.reduce((sum, s) => sum + (s.status === 'online' ? s.playerCount : 0), 0)}
</div>
<div style={{
color: 'var(--text-secondary)',
fontSize: '1.1rem'
}}>
{t('servers.totalPlayers')}
</div>
</div>
<div>
<div style={{
fontSize: '2.5rem',
fontWeight: 'bold',
color: 'var(--accent-primary)',
marginBottom: '0.5rem'
}}>
{Array.from(new Set(servers.map(s => s.category))).length}
</div>
<div style={{
color: 'var(--text-secondary)',
fontSize: '1.1rem'
}}>
{t('servers.categories')}
</div>
</div>
<div>
<div style={{
fontSize: '2.5rem',
fontWeight: 'bold',
color: 'var(--accent-primary)',
marginBottom: '0.5rem'
}}>
{servers.length}
</div>
<div style={{
color: 'var(--text-secondary)',
fontSize: '1.1rem'
}}>
{t('servers.totalServers')}
</div>
</div>
</div>
</div>
</section>
</Layout>
)
}
export default Servers