Add loading skeletons for server data display in Servers component
This commit is contained in:
@@ -32,6 +32,37 @@ interface ServerData extends ServerInfo {
|
|||||||
error?: string
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
function Servers() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@@ -49,6 +80,7 @@ function Servers() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const [servers, setServers] = useState<ServerData[]>([])
|
const [servers, setServers] = useState<ServerData[]>([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string>('All')
|
const [selectedCategory, setSelectedCategory] = useState<string>('All')
|
||||||
|
|
||||||
// Fetch A2S data for all servers
|
// Fetch A2S data for all servers
|
||||||
@@ -84,6 +116,7 @@ function Servers() {
|
|||||||
|
|
||||||
const results = await Promise.all(serverPromises)
|
const results = await Promise.all(serverPromises)
|
||||||
setServers(results)
|
setServers(results)
|
||||||
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchServerData()
|
fetchServerData()
|
||||||
@@ -174,7 +207,69 @@ function Servers() {
|
|||||||
gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))',
|
gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))',
|
||||||
gap: '2rem'
|
gap: '2rem'
|
||||||
}}>
|
}}>
|
||||||
{filteredServers.map((server) => (
|
{loading ? (
|
||||||
|
// Show loading skeletons
|
||||||
|
serverList.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
|
<div
|
||||||
key={`${server.ip}:${server.port}`}
|
key={`${server.ip}:${server.port}`}
|
||||||
className="server-card"
|
className="server-card"
|
||||||
@@ -328,7 +423,8 @@ function Servers() {
|
|||||||
{server.status === 'online' ? t('servers.joinServer') : t('servers.offline')}
|
{server.status === 'online' ? t('servers.joinServer') : t('servers.offline')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user