import React, { useState, useEffect } from 'react'; import { Box, Container, Typography, Button, TextField, InputAdornment, IconButton, AppBar, Toolbar, Avatar, Tooltip, Dialog, DialogTitle, DialogContent, DialogActions, CircularProgress, Paper, Chip, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Switch, FormControlLabel, Divider, List, ListItem, ListItemText, ListItemSecondaryAction, Select, MenuItem, FormControl, InputLabel, Tabs, Tab } from '@mui/material'; import SearchIcon from '@mui/icons-material/Search'; import AddIcon from '@mui/icons-material/Add'; import SteamIcon from '@mui/icons-material/SportsEsports'; import DeleteIcon from '@mui/icons-material/Delete'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import SyncIcon from '@mui/icons-material/Sync'; import BoltIcon from '@mui/icons-material/Bolt'; import TimerIcon from '@mui/icons-material/Timer'; import LockResetIcon from '@mui/icons-material/LockReset'; import SettingsIcon from '@mui/icons-material/Settings'; import ShareIcon from '@mui/icons-material/Share'; import GroupAddIcon from '@mui/icons-material/GroupAdd'; import ShieldIcon from '@mui/icons-material/Shield'; import GppBadIcon from '@mui/icons-material/GppBad'; import PeopleIcon from '@mui/icons-material/People'; import VerifiedUserIcon from '@mui/icons-material/VerifiedUser'; import WorkspacePremiumIcon from '@mui/icons-material/WorkspacePremium'; import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings'; import StorageIcon from '@mui/icons-material/Storage'; import GroupIcon from '@mui/icons-material/Group'; import AccountTreeIcon from '@mui/icons-material/AccountTree'; import { useAccounts, type Account } from '../hooks/useAccounts'; import { useAppTheme } from '../theme/ThemeContext'; import type { ThemeType } from '../theme/SteamTheme'; import NebulaBanner from '../components/NebulaBanner'; const AdminPanel: React.FC<{ open: boolean, onClose: () => void }> = ({ open, onClose }) => { const { adminGetStats, adminGetUsers, adminDeleteUser, adminGetAccounts, adminRemoveAccount } = useAccounts(); const [tab, setTab] = useState(0); const [stats, setStats] = useState(null); const [users, setUsers] = useState([]); const [accounts, setAccounts] = useState([]); const [loading, setLoading] = useState(false); const loadData = async () => { setLoading(true); try { if (tab === 0) setStats(await adminGetStats()); if (tab === 1) setUsers(await adminGetUsers()); if (tab === 2) setAccounts(await adminGetAccounts()); } catch (e) {} setLoading(false); }; useEffect(() => { if (open) loadData(); }, [open, tab]); const handleDeleteUser = async (id: string) => { if (window.confirm("Wipe this user and all their accounts?")) { await adminDeleteUser(id); loadData(); } }; const handleForceRemove = async (steamId: string) => { if (window.confirm("Force remove this account from server?")) { await adminRemoveAccount(steamId); loadData(); } }; return ( Server Administration setTab(v)} sx={{ bgcolor: 'background.paper', borderBottom: 1, borderColor: 'divider' }}> } label="Overview" /> } label="Users" /> } label="Global Accounts" /> {loading ? : ( <> {tab === 0 && stats && ( {[ { label: 'Total Users', value: stats.users }, { label: 'Total Accounts', value: stats.accounts }, { label: 'Active Cooldowns', value: stats.activeCooldowns } ].map((s) => ( {s.value} {s.label} ))} )} {tab === 1 && ( {users.map(u => ( handleDeleteUser(u._id)}> ))} )} {tab === 2 && ( {accounts.map(a => ( handleForceRemove(a.steamId)}> ))} )} )} ); }; const Dashboard: React.FC = () => { const { currentTheme, setTheme } = useAppTheme(); const { accounts, isLoading, isSyncing, serverConfig, deleteAccount, switchAccount, openSteamAppLogin, openSteamLogin, updateServerConfig, loginToServer, syncNow } = useAccounts(); const [searchTerm, setSearchTerm] = useState(''); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isAdminPanelOpen, setIsAdminPanelOpen] = useState(false); const [serverUrl, setServerUrl] = useState(''); useEffect(() => { if (serverConfig?.url) setServerUrl(serverConfig.url); }, [serverConfig?.url]); const saveSettings = async () => { await updateServerConfig({ url: serverUrl }); alert("Server URL updated!"); }; const safeAccounts = Array.isArray(accounts) ? accounts : []; const filteredAccounts = safeAccounts.filter((acc) => { if (!acc) return false; const name = acc.personaName || 'Unknown'; const id = acc.steamId || ''; return name.toLowerCase().includes(searchTerm.toLowerCase()) || id.includes(searchTerm); }); return ( ULTIMATE BAN TRACKER {/* Admin Button - Only visible if isAdmin is true */} {serverConfig?.isAdmin && ( setIsAdminPanelOpen(true)}> )} {isSyncing ? ( ) : ( syncNow()} disabled={!serverConfig?.enabled} sx={{ color: 'primary.main' }}> )} setSearchTerm(e.target.value)} sx={{ backgroundColor: 'rgba(255, 255, 255, 0.05)', borderRadius: 1, width: 200, '& .MuiOutlinedInput-root': { '& fieldset': { border: 'none' }, height: 32 } }} InputProps={{ startAdornment: ( ), }} /> setIsSettingsOpen(true)}> {isLoading ? ( ) : ( AVATAR ACCOUNT BAN STATUS COOLDOWN ACTIONS {filteredAccounts.map((account) => ( openSteamLogin(account.steamId)} /> ))}
)} {!isLoading && filteredAccounts.length === 0 && ( No accounts tracked. Click "Add" to get started! )}
{/* Settings Dialog */} setIsSettingsOpen(false)} maxWidth="sm" fullWidth> Settings & Customization THEME SELECTION Active Theme BACKEND CONFIGURATION setServerUrl(e.target.value)} placeholder="https://ultimate-ban-tracker.narl.io" margin="dense" sx={{ mb: 2 }} InputProps={{ endAdornment: ( ), }} /> COMMUNITY AUTHENTICATION {serverConfig?.token ? "Connected to Server" : "Not Authenticated"} {serverConfig?.token ? "Your accounts can now be shared with others." : "Login to share and sync with your community."} {serverConfig?.token && ( )} updateServerConfig({ enabled: e.target.checked })} disabled={!serverConfig?.token} /> } label="Enable Community Sync" sx={{ mt: 2 }} /> {/* Admin Panel */} setIsAdminPanelOpen(false)} />
); }; // --- Sub-Component: AccountRow --- const AccountRow: React.FC<{ account: Account, onDelete: (id: string) => void, onSwitch: (login: string) => void, onAuth: () => void }> = ({ account, onDelete, onSwitch, onAuth }) => { const { shareAccountWithUser, revokeAccountAccess, revokeAllAccountAccess, getServerUsers, serverConfig, scrapeAccount } = useAccounts(); const [timeLeft, setTimeLeft] = useState(null); const [isShareOpen, setIsShareOpen] = useState(false); const [targetUserId, setTargetUserId] = useState(''); const [isSharing, setIsSharing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [serverUsers, setServerUsers] = useState([]); 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 targetTime = cooldownDate.getTime(); const timer = setInterval(() => { const diff = targetTime - Date.now(); 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]); const avatarSrc = account?.localAvatar ? `steam-resource://${account.localAvatar}` : (account?.avatar || ''); const [imgSrc, setImgSrc] = useState(avatarSrc); useEffect(() => { setImgSrc(avatarSrc); }, [avatarSrc]); const handleRefresh = async () => { setIsRefreshing(true); await scrapeAccount(account.steamId); setIsRefreshing(false); }; const handleOpenShare = async () => { setIsShareOpen(true); try { const [users, selfInfo] = await Promise.all([ getServerUsers(), (window as any).electronAPI.getServerUserInfo() ]); const filtered = (Array.isArray(users) ? users : []).filter(u => u.steamId !== selfInfo.steamId && u.steamId !== account.steamId ); setServerUsers(filtered); } catch (e) {} }; const handleShare = async () => { if (!targetUserId) return; setIsSharing(true); try { await shareAccountWithUser(account.steamId, targetUserId); setTargetUserId(''); } catch (e: any) { alert(e.message || "Failed to share account"); } finally { setIsSharing(false); } }; const handleRevoke = async (targetSteamId: string) => { if (!window.confirm("Revoke access for this user?")) return; try { await revokeAccountAccess(account.steamId, targetSteamId); } catch (e: any) { alert(e.message); } }; const handleRevokeAll = async () => { if (!window.confirm("Completely stop sharing this account?")) return; try { await revokeAllAccountAccess(account.steamId); setIsShareOpen(false); } catch (e: any) { alert(e.message); } }; const isBanned = account?.vacBanned || (account?.gameBans && account.gameBans > 0); // Primary account check const isPrimaryAccount = serverConfig?.serverSteamId === account.steamId; // Refined Shared Logic const isSharedWithYou = account?._id.startsWith('shared_'); const hasSharedMembers = (account as any).sharedWith && (account as any).sharedWith.length > 0; const showCommunityIcon = isSharedWithYou || hasSharedMembers; return ( {isPrimaryAccount && ( )} {showCommunityIcon && ( )} {account?.personaName || 'Unknown'} {account?.steamId} {isBanned ? ( BANNED {account?.vacBanned && } {account?.gameBans ? account.gameBans > 0 && : null} ) : ( SECURE )} {account?.authError ? ( Needs Re-auth ) : isCooldownActive ? ( {timeLeft} ) : ( Available )} {account.loginName && ( )} {account.steamLoginSecure && !account.authError ? : (account.authError ? : )} {account.steamLoginSecure && !account.authError && ( TRACKING {isRefreshing ? : } )} (window as any).electronAPI.openExternal(account?.profileUrl || '')}> onDelete(account?._id || '')}> setIsShareOpen(false)} maxWidth="xs" fullWidth> Permissions GRANT ACCESS Select User CURRENT ACCESS {(account as any).sharedWith?.map((sw: any) => ( handleRevoke(sw.steamId)}> ))} {(!(account as any).sharedWith || (account as any).sharedWith.length === 0) && ( Not shared with anyone yet. )} {(account as any).sharedWith?.length > 0 && ( )} ); }; export default Dashboard;