Files
ultimate-ban-tracker/frontend/src/components/SteamCard.tsx
Nils Pukropp 64fe49e58e
Some checks failed
Build and Release / build (push) Has been cancelled
init
2026-02-21 01:48:48 +01:00

199 lines
7.0 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
Card, CardContent, Typography, Box, Avatar, IconButton, Tooltip,
Chip, Dialog, DialogTitle, DialogContent,
TextField, DialogActions, Button
} from '@mui/material';
import BoltIcon from '@mui/icons-material/Bolt';
import TimerIcon from '@mui/icons-material/Timer';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import DeleteIcon from '@mui/icons-material/Delete';
interface SteamCardProps {
account: {
_id: string;
steamId: string;
personaName: string;
loginName?: string;
steamLoginSecure?: string;
autoCheckCooldown: boolean;
accountType: 'owned' | 'watched';
avatar: string;
profileUrl?: string;
status?: string;
vacBanned: boolean;
gameBans: number;
lastBanCheck: string;
cooldownExpiresAt?: string;
};
onDelete: (id: string) => void;
onUpdate: (id: string, data: any) => void;
onFastLogin: () => void;
}
const SteamCard: React.FC<SteamCardProps> = ({ account, onDelete, onUpdate, onFastLogin }) => {
const [timeLeft, setTimeLeft] = useState<string | null>(null);
const [isLoginNameDialogOpen, setIsLoginNameDialogOpen] = useState(false);
const [tempLoginName, setTempLoginName] = useState(account.loginName || '');
const isOwned = account.accountType === 'owned';
const isPermanentlyBanned = account.vacBanned || account.gameBans > 0 || account.status === 'banned';
const cooldownDate = account.cooldownExpiresAt ? new Date(account.cooldownExpiresAt) : null;
const isCooldownActive = cooldownDate && !isNaN(cooldownDate.getTime()) && cooldownDate.getTime() > Date.now();
useEffect(() => {
if (!isCooldownActive || !cooldownDate) {
setTimeLeft(null);
return;
}
const timer = setInterval(() => {
const now = new Date();
const diff = cooldownDate.getTime() - now.getTime();
if (diff <= 0) {
setTimeLeft(null);
clearInterval(timer);
return;
}
const hours = Math.floor(diff / 3600000);
const mins = Math.floor((diff % 3600000) / 60000);
const secs = Math.floor((diff % 60000) / 1000);
setTimeLeft(`${hours}h ${mins}m ${secs}s`);
}, 1000);
return () => clearInterval(timer);
}, [account.cooldownExpiresAt, isCooldownActive, cooldownDate]);
const getStatusColor = () => {
if (isPermanentlyBanned) return '#af3212';
if (isCooldownActive) return '#ff9800';
return '#5c7e10';
};
const getStatusText = () => {
if (account.vacBanned) return 'VAC BANNED';
if (account.gameBans > 0) return `${account.gameBans} GAME BAN${account.gameBans > 1 ? 'S' : ''}`;
if (isPermanentlyBanned) return 'BANNED';
if (isCooldownActive) return 'COOLDOWN';
return 'AVAILABLE';
};
const handleFastLoginClick = () => {
if (!account.loginName) {
setIsLoginNameDialogOpen(true);
return;
}
onFastLogin();
};
return (
<Card sx={{
position: 'relative',
transition: 'transform 0.2s, box-shadow 0.2s',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: `0 0 20px rgba(102, 192, 244, 0.2)`
},
borderTop: `4px solid ${getStatusColor()}`
}}>
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'flex-start' }}>
<Avatar
src={account.avatar}
variant="square"
sx={{
width: 64,
height: 64,
border: `2px solid ${getStatusColor()}`,
boxShadow: isPermanentlyBanned ? '0 0 10px rgba(175, 50, 18, 0.4)' : 'none'
}}
/>
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<Typography variant="h6" noWrap sx={{ color: '#ffffff', mb: 0.5, maxWidth: '140px' }}>
{account.personaName || 'Unknown'}
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Chip
label={getStatusText()}
size="small"
sx={{
backgroundColor: getStatusColor(),
color: '#ffffff',
fontWeight: 'bold',
fontSize: '0.65rem',
height: 20
}}
/>
{timeLeft && (
<Typography variant="caption" sx={{ color: '#ff9800', fontWeight: 'bold', display: 'flex', alignItems: 'center' }}>
<TimerIcon sx={{ fontSize: 12, mr: 0.5 }} /> {timeLeft}
</Typography>
)}
</Box>
</Box>
</Box>
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
{isOwned && (
<Button
variant="contained"
size="small"
onClick={handleFastLoginClick}
sx={{
minWidth: 'unset',
px: 1,
py: 0.2,
fontSize: '0.7rem',
background: account.loginName ? 'linear-gradient(to bottom, #8ed629 5%, #5c7e10 95%)' : 'rgba(255,255,255,0.1)'
}}
>
<BoltIcon sx={{ fontSize: 14, mr: 0.5 }} /> LOGIN
</Button>
)}
</Box>
<Box sx={{ display: 'flex', gap: 0.5 }}>
<Tooltip title="View Profile">
<IconButton size="small" component="a" href={account.profileUrl || '#'} target="_blank" rel="noopener noreferrer">
<OpenInNewIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Remove Tracker">
<IconButton size="small" color="error" onClick={() => onDelete(account._id)}>
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
</Box>
<Dialog open={isLoginNameDialogOpen} onClose={() => setIsLoginNameDialogOpen(false)}>
<DialogTitle sx={{ backgroundColor: '#1b2838', color: '#ffffff' }}>Steam Login Name</DialogTitle>
<DialogContent sx={{ backgroundColor: '#1b2838', pt: 2 }}>
<TextField
fullWidth
autoFocus
label="Steam Username"
value={tempLoginName}
onChange={(e) => setTempLoginName(e.target.value)}
/>
</DialogContent>
<DialogActions sx={{ backgroundColor: '#1b2838', p: 2 }}>
<Button onClick={() => setIsLoginNameDialogOpen(false)} color="inherit">Cancel</Button>
<Button onClick={() => { onUpdate(account._id, { loginName: tempLoginName }); setIsLoginNameDialogOpen(false); }} variant="contained" color="primary">Save</Button>
</DialogActions>
</Dialog>
</CardContent>
</Card>
);
};
export default SteamCard;