diff --git a/admin/index.php b/admin/index.php index 5b08159..7147a47 100644 --- a/admin/index.php +++ b/admin/index.php @@ -793,8 +793,8 @@ tr:hover td{background:rgba(255,255,255,.015)} - -
+ + + + + +
@@ -1463,13 +1478,26 @@ function renderGamerProfile(u) { // Update token display in tokens tab document.getElementById('gm-token-display').textContent = parseFloat(u.tokens||0) + ' 🪙'; // Update suspend button - const sb = document.getElementById('gm-suspend-btn'); - if (u.status === 'active') { - sb.textContent = '🔒 SUSPEND ACCOUNT (Lock Login)'; - sb.className = 'btn btn-red'; + const sb = document.getElementById('gm-suspend-btn'); + const db2 = document.querySelector('[onclick="gmDeleteAccount()"]'); + const isMasterUser = parseInt(u.id) === MASTER_ADMIN_ID; + if (isMasterUser) { + sb.textContent = '🔒 Cannot Suspend Master Admin'; + sb.className = 'btn'; + sb.disabled = true; + sb.style.cssText = 'opacity:.4;cursor:not-allowed;background:rgba(255,68,68,.06);color:var(--red);border:1px solid rgba(255,68,68,.2)'; + if (db2) { db2.disabled = true; db2.style.opacity = '.4'; db2.style.cursor = 'not-allowed'; db2.title = 'Cannot delete master admin account'; } } else { - sb.textContent = '🔓 ACTIVATE ACCOUNT (Restore Login)'; - sb.className = 'btn btn-green'; + sb.disabled = false; + sb.style.cssText = ''; + if (db2) { db2.disabled = false; db2.style.opacity = ''; db2.style.cursor = ''; db2.title = ''; } + if (u.status === 'active') { + sb.textContent = '🔒 SUSPEND ACCOUNT (Lock Login)'; + sb.className = 'btn btn-red'; + } else { + sb.textContent = '🔓 ACTIVATE ACCOUNT (Restore Login)'; + sb.className = 'btn btn-green'; + } } } @@ -1714,6 +1742,7 @@ async function gmSaveEdit() { } async function gmToggleSuspend() { + if (GM.current && parseInt(GM.current.id) === MASTER_ADMIN_ID) { toast('Cannot suspend master admin','err'); return; } const isSuspended = GM.current.status === 'suspended'; const action = isSuspended ? 'activate' : 'suspend'; if (!isSuspended && !confirm(`Suspend ${GM.current.username}? They will be locked out immediately.`)) return; @@ -1790,6 +1819,7 @@ function updateAdminToggleBtn() { } async function gmDeleteAccount() { + if (GM.current && parseInt(GM.current.id) === MASTER_ADMIN_ID) { toast('Cannot delete master admin account','err'); return; } if (!confirm(`⚠️ PERMANENTLY delete ${GM.current.username}?\n\nThis removes all their data and cannot be undone.`)) return; if (!confirm(`Are you absolutely sure? Type OK to confirm deletion of ${GM.current.username}.`)) return; const d = await apiFetch('delete_user','POST',{user_id:GM.current.id}); @@ -2887,27 +2917,72 @@ async function loadGames() { function editGame(id) { const g = (window._gamesData || []).find(x => x.id == id); if (!g) return; - document.getElementById('gf-id').value = g.id; - document.getElementById('gf-name').value = g.name; - document.getElementById('gf-slug').value = g.slug; - document.getElementById('gf-slug').disabled = true; // slug can't change (used as FK in purchases) - document.getElementById('gf-player-url').value = g.player_url; - document.getElementById('gf-agent-link').value = g.agent_link || ''; - document.getElementById('gf-agent-login').value = g.agent_login || ''; - document.getElementById('gf-agent-password').value= g.agent_password || ''; - document.getElementById('gf-games-link').value = g.games_link || ''; - document.getElementById('gf-agent-guide').value = g.agent_guide || ''; - document.getElementById('gf-sub-agent-login').value = g.sub_agent_login || ''; - document.getElementById('gf-sub-agent-password').value = g.sub_agent_password || ''; - document.getElementById('gf-cashier-login').value = g.cashier_login || ''; - document.getElementById('gf-cashier-password').value = g.cashier_password || ''; - document.getElementById('gf-color').value = g.color || '#f0c040'; + document.getElementById('gf-id').value = g.id; + document.getElementById('gf-name').value = g.name; + document.getElementById('gf-slug').value = g.slug; + document.getElementById('gf-slug').disabled = true; + document.getElementById('gf-player-url').value = g.player_url; + document.getElementById('gf-color').value = g.color || '#f0c040'; document.getElementById('gf-color-hex').value = g.color || '#f0c040'; document.getElementById('gf-sort').value = g.sort_order; document.getElementById('gf-active').value = g.is_active; document.getElementById('game-form-title').textContent = '✏️ Editing: ' + g.name; - document.getElementById('gf-credit-btn').disabled = false; - // Load credit total for this platform + + if (IS_MASTER_ADMIN) { + // Show edit mode, hide view mode + document.getElementById('gf-agent-edit').style.display = 'block'; + document.getElementById('gf-agent-view').style.display = 'none'; + document.getElementById('gf-agent-link').value = g.agent_link || ''; + document.getElementById('gf-agent-login').value = g.agent_login || ''; + document.getElementById('gf-agent-password').value = g.agent_password || ''; + document.getElementById('gf-games-link').value = g.games_link || ''; + document.getElementById('gf-agent-guide').value = g.agent_guide || ''; + document.getElementById('gf-sub-agent-login').value = g.sub_agent_login || ''; + document.getElementById('gf-sub-agent-password').value = g.sub_agent_password || ''; + document.getElementById('gf-cashier-login').value = g.cashier_login || ''; + document.getElementById('gf-cashier-password').value = g.cashier_password || ''; + document.getElementById('gf-credit-btn').disabled = false; + } else { + // Show view mode, hide edit mode + document.getElementById('gf-agent-edit').style.display = 'none'; + document.getElementById('gf-agent-view').style.display = 'block'; + document.getElementById('gf-credit-btn').disabled = true; + + const agentFields = [ + {label:'Agent Login', key:'agent_login', isUrl:false}, + {label:'Agent Password', key:'agent_password', isUrl:false}, + {label:'Agent Link', key:'agent_link', isUrl:true}, + {label:'Games Link', key:'games_link', isUrl:true}, + {label:'Agent Guide', key:'agent_guide', isUrl:false}, + {label:'Sub-Account Agent Login', key:'sub_agent_login', isUrl:false}, + {label:'Sub-Account Agent Password',key:'sub_agent_password', isUrl:false}, + {label:'Cashier Login', key:'cashier_login', isUrl:false}, + {label:'Cashier Password', key:'cashier_password', isUrl:false}, + ]; + const content = document.getElementById('gf-agent-view-content'); + content.innerHTML = agentFields.map(f => { + const val = g[f.key] || ''; + if (!val) return ''; + const openBtn = f.isUrl ? ` + ↗ Open + ` : ''; + return `
+ ${escHtmlA(f.label)} + ${escHtmlA(val)} + ${openBtn} + +
`; + }).join(''); + if (!content.innerHTML.trim()) { + content.innerHTML = '
No agent info has been set for this game.
'; + } + } + + // Load credit total fetch('/api/platforms.php?action=credits_list&platform_id=' + g.id).then(r=>r.json()).then(d=>{ if (d.success) { const t = d.total||0; @@ -2922,22 +2997,27 @@ function resetGameForm() { document.getElementById('gf-name').value = ''; document.getElementById('gf-slug').value = ''; document.getElementById('gf-slug').disabled = false; - document.getElementById('gf-player-url').value = ''; - document.getElementById('gf-agent-link').value = ''; - document.getElementById('gf-agent-login').value = ''; - document.getElementById('gf-agent-password').value= ''; - document.getElementById('gf-games-link').value = ''; - document.getElementById('gf-agent-guide').value = ''; - document.getElementById('gf-sub-agent-login').value = ''; - document.getElementById('gf-sub-agent-password').value = ''; - document.getElementById('gf-cashier-login').value = ''; - document.getElementById('gf-cashier-password').value = ''; - document.getElementById('gf-color').value = '#f0c040'; + document.getElementById('gf-player-url').value = ''; + document.getElementById('gf-color').value = '#f0c040'; document.getElementById('gf-color-hex').value = '#f0c040'; document.getElementById('gf-sort').value = '99'; document.getElementById('gf-active').value = '1'; document.getElementById('gf-credit-total').textContent = '—'; document.getElementById('gf-credit-btn').disabled = true; + // Always hide both agent panels when clearing + document.getElementById('gf-agent-edit').style.display = 'none'; + document.getElementById('gf-agent-view').style.display = 'none'; + if (IS_MASTER_ADMIN) { + document.getElementById('gf-agent-link').value = ''; + document.getElementById('gf-agent-login').value = ''; + document.getElementById('gf-agent-password').value = ''; + document.getElementById('gf-games-link').value = ''; + document.getElementById('gf-agent-guide').value = ''; + document.getElementById('gf-sub-agent-login').value = ''; + document.getElementById('gf-sub-agent-password').value = ''; + document.getElementById('gf-cashier-login').value = ''; + document.getElementById('gf-cashier-password').value = ''; + } document.getElementById('game-form-title').textContent = '➕ Add New Game'; document.getElementById('game-form-alert').className = 'alert'; } @@ -3483,6 +3563,29 @@ function formatAdminDateLabel(dateStr) { } function escHtmlA(s) { return String(s||'').replace(/&/g,'&').replace(//g,'>').replace(/\n/g,'
'); } + +function openFieldUrl(inputId) { + const url = document.getElementById(inputId)?.value?.trim(); + if (!url) return false; + window.open(url, '_blank', 'noopener'); + return false; +} + +function copyToClipboard(text, btn) { + navigator.clipboard.writeText(text).then(() => { + const orig = btn.innerHTML; + btn.innerHTML = '✅ Copied'; + btn.style.background = 'rgba(0,230,118,0.15)'; + btn.style.borderColor = 'rgba(0,230,118,0.4)'; + btn.style.color = 'var(--green)'; + setTimeout(() => { + btn.innerHTML = orig; + btn.style.background = ''; + btn.style.borderColor = ''; + btn.style.color = ''; + }, 1800); + }).catch(() => toast('Copy failed — try manually','err')); +} function escAttr(s) { return String(s||'').replace(/'/g,"\\'").replace(/"/g,'"'); } // Poll chat badge every 10s diff --git a/api/platforms.php b/api/platforms.php index ae19366..0cf8929 100644 --- a/api/platforms.php +++ b/api/platforms.php @@ -4,8 +4,9 @@ try { require_once __DIR__ . '/../../includes/auth.php'; } catch(Throwable $e) { ob_end_clean(); header('Content-Type: application/json'); -$action = $_GET['action'] ?? 'list'; -$isAdmin = isLoggedIn() && !empty($_SESSION['is_admin']); +$action = $_GET['action'] ?? 'list'; +$isAdmin = isLoggedIn() && !empty($_SESSION['is_admin']); +$isMasterAdmin = $isAdmin && (int)($_SESSION['user_id'] ?? 0) === MASTER_ADMIN_ID; switch ($action) { @@ -79,8 +80,15 @@ switch ($action) { $sort_order = (int)($d['sort_order'] ?? 99); $is_active = (int)(bool)($d['is_active'] ?? 1); if (!$id || !$name || !$player_url) { echo json_encode(['success'=>false,'error'=>'ID, name, and player URL required']); exit; } - db()->prepare("UPDATE platforms SET name=?,player_url=?,agent_link=?,agent_login=?,agent_password=?,games_link=?,agent_guide=?,sub_agent_login=?,sub_agent_password=?,cashier_login=?,cashier_password=?,color=?,sort_order=?,is_active=? WHERE id=?") - ->execute([$name,$player_url,$agent_link,$agent_login,$agent_password,$games_link,$agent_guide,$sub_agent_login,$sub_agent_password,$cashier_login,$cashier_password,$color,$sort_order,$is_active,$id]); + if ($isMasterAdmin) { + // Master admin: update all fields including agent info + db()->prepare("UPDATE platforms SET name=?,player_url=?,agent_link=?,agent_login=?,agent_password=?,games_link=?,agent_guide=?,sub_agent_login=?,sub_agent_password=?,cashier_login=?,cashier_password=?,color=?,sort_order=?,is_active=? WHERE id=?") + ->execute([$name,$player_url,$agent_link,$agent_login,$agent_password,$games_link,$agent_guide,$sub_agent_login,$sub_agent_password,$cashier_login,$cashier_password,$color,$sort_order,$is_active,$id]); + } else { + // Regular admin: update only non-sensitive fields + db()->prepare("UPDATE platforms SET name=?,player_url=?,color=?,sort_order=?,is_active=? WHERE id=?") + ->execute([$name,$player_url,$color,$sort_order,$is_active,$id]); + } echo json_encode(['success'=>true]); break; @@ -119,7 +127,7 @@ switch ($action) { // ── Admin: add credit entry ─────────────────────────── case 'credits_create': - if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; } + if (!$isMasterAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; } $d = json_decode(file_get_contents('php://input'), true); $pid = (int)($d['platform_id'] ?? 0); $credits = (float)($d['credits_purchased'] ?? 0); @@ -137,7 +145,7 @@ switch ($action) { // ── Admin: update credit entry ──────────────────────── case 'credits_update': - if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; } + if (!$isMasterAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; } $d = json_decode(file_get_contents('php://input'), true); $id = (int)($d['id'] ?? 0); $credits = (float)($d['credits_purchased'] ?? 0); @@ -157,7 +165,7 @@ switch ($action) { // ── Admin: delete credit entry ──────────────────────── case 'credits_delete': - if (!$isAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; } + if (!$isMasterAdmin || $_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['success'=>false,'error'=>'Forbidden']); exit; } $d = json_decode(file_get_contents('php://input'), true); $id = (int)($d['id'] ?? 0); if (!$id) { echo json_encode(['success'=>false,'error'=>'ID required']); exit; }