199 lines
7.0 KiB
TypeScript
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;
|