Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>FriendlyBot — Roster List</title> | |
| <link rel="icon" href="/logo.png" /> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" /> | |
| <style> | |
| :root { | |
| --bg: #0a0a1a; | |
| --surface: #12122a; | |
| --border: rgba(255, 255, 255, 0.06); | |
| --text: #fff; | |
| --text-dim: rgba(255, 255, 255, 0.6); | |
| --accent: #5865F2; | |
| --radius: 16px; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: "Inter", sans-serif; | |
| background: var(--bg); | |
| color: var(--text); | |
| min-height: 100vh; | |
| padding: 40px 20px; | |
| } | |
| header { | |
| max-width: 1000px; | |
| margin: 0 auto 40px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| h1 { font-size: 28px; font-weight: 700; } | |
| .btn { | |
| display: inline-block; | |
| padding: 10px 20px; | |
| background: var(--accent); | |
| color: #fff; | |
| text-decoration: none; | |
| border-radius: 8px; | |
| font-weight: 600; | |
| font-size: 14px; | |
| } | |
| .container { max-width: 1000px; margin: 0 auto; } | |
| .team-section { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| padding: 24px; | |
| margin-bottom: 24px; | |
| } | |
| .team-title { | |
| font-size: 20px; | |
| font-weight: 700; | |
| margin-bottom: 16px; | |
| color: var(--accent); | |
| } | |
| table { width: 100%; border-collapse: collapse; } | |
| th, td { | |
| text-align: left; | |
| padding: 12px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| th { | |
| color: var(--text-dim); | |
| font-size: 12px; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| tr:last-child td { border-bottom: none; } | |
| .empty { | |
| text-align: center; | |
| color: var(--text-dim); | |
| padding: 40px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>📋 Roster List</h1> | |
| <a href="/" class="btn">Back to Dashboard</a> | |
| </header> | |
| <div class="container" id="app"> | |
| <div class="empty">Loading rosters...</div> | |
| </div> | |
| <script> | |
| async function loadRosters() { | |
| try { | |
| const res = await fetch("/api/roster"); | |
| const data = await res.json(); | |
| const app = document.getElementById("app"); | |
| if (!data || Object.keys(data).length === 0) { | |
| app.innerHTML = '<div class="empty">No rosters found. Use !roster-add @user Role in Discord to add one.</div>'; | |
| return; | |
| } | |
| let html = ""; | |
| Object.entries(data).forEach(([team, members]) => { | |
| html += `<div class="team-section"> | |
| <div class="team-title">${team}</div> | |
| <table> | |
| <thead> | |
| <tr><th>User</th><th>Role</th><th>Added</th></tr> | |
| </thead> | |
| <tbody> | |
| ${members.map(m => ` | |
| <tr> | |
| <td>${escapeHtml(m.user)}</td> | |
| <td>${escapeHtml(m.role)}</td> | |
| <td>${escapeHtml(m.addedAt)}</td> | |
| </tr> | |
| `).join("")} | |
| </tbody> | |
| </table> | |
| </div>`; | |
| }); | |
| app.innerHTML = html; | |
| } catch (err) { | |
| document.getElementById("app").innerHTML = '<div class="empty">Failed to load rosters.</div>'; | |
| } | |
| } | |
| function escapeHtml(str) { | |
| if (!str) return ""; | |
| const d = document.createElement("div"); | |
| d.textContent = str; | |
| return d.innerHTML; | |
| } | |
| loadRosters(); | |
| </script> | |
| </body> | |
| </html> |