This commit is contained in:
198
frontend/src/components/SteamCard.tsx
Normal file
198
frontend/src/components/SteamCard.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user