AeThex-Bot-Master/aethex-bot/public/dashboard.html
sirpiglr 9d1f73f3ad Update application theme to use a purple color scheme
Replaces blue color gradients and variables with purple alternatives across CSS and HTML files to align with the desired application theme.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: f9f1a4eb-2832-4cc3-92fc-6f966658f686
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/K1dCpNm
Replit-Helium-Checkpoint-Created: true
2025-12-09 00:31:54 +00:00

1115 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - AeThex Bot</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--background: hsl(270, 50%, 4%);
--foreground: hsl(270, 20%, 98%);
--card: hsl(270, 50%, 6%);
--card-hover: hsl(270, 50%, 8%);
--primary: hsl(270, 100%, 60%);
--secondary: hsl(270, 30%, 15%);
--muted: hsl(270, 30%, 15%);
--muted-foreground: hsl(270, 15%, 65%);
--border: hsl(270, 30%, 20%);
--aethex-400: hsl(270, 100%, 75%);
--aethex-500: hsl(270, 100%, 65%);
--neon-purple: hsl(280, 100%, 70%);
--neon-violet: hsl(270, 100%, 70%);
--neon-magenta: hsl(300, 100%, 70%);
--success: hsl(142, 76%, 36%);
--warning: hsl(38, 92%, 50%);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--background);
color: var(--foreground);
min-height: 100vh;
}
.wallpaper {
position: fixed;
inset: 0;
background-image: linear-gradient(135deg, rgba(147, 51, 234, 0.08) 0%, rgba(168, 85, 247, 0.05) 100%);
pointer-events: none;
z-index: -1;
}
.app {
display: flex;
min-height: 100vh;
}
.sidebar {
width: 260px;
background: rgba(10, 10, 20, 0.95);
border-right: 1px solid var(--border);
padding: 1.5rem;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
overflow-y: auto;
}
.logo {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 2rem;
text-decoration: none;
color: var(--foreground);
}
.logo-icon {
width: 36px;
height: 36px;
background: linear-gradient(135deg, var(--aethex-500), var(--neon-purple));
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
}
.logo-text {
font-size: 1.25rem;
font-weight: 700;
background: linear-gradient(to right, var(--aethex-400), var(--neon-purple));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.guild-selector {
margin-bottom: 1.5rem;
}
.guild-selector select {
width: 100%;
padding: 0.75rem;
background: var(--secondary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--foreground);
font-size: 0.875rem;
cursor: pointer;
}
.nav-section {
margin-bottom: 1.5rem;
}
.nav-section-title {
font-size: 0.75rem;
text-transform: uppercase;
color: var(--muted-foreground);
margin-bottom: 0.75rem;
font-weight: 600;
letter-spacing: 0.05em;
}
.nav-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
border-radius: 8px;
color: var(--muted-foreground);
text-decoration: none;
transition: all 0.2s;
cursor: pointer;
margin-bottom: 0.25rem;
}
.nav-item:hover, .nav-item.active {
background: var(--secondary);
color: var(--foreground);
}
.nav-item.active {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.2), rgba(59, 130, 246, 0.1));
border: 1px solid rgba(139, 92, 246, 0.3);
}
.nav-icon { width: 20px; text-align: center; }
.user-card {
margin-top: auto;
padding: 1rem;
background: var(--secondary);
border-radius: 12px;
display: flex;
align-items: center;
gap: 0.75rem;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--aethex-500);
}
.user-info {
flex: 1;
min-width: 0;
}
.user-name {
font-weight: 600;
font-size: 0.875rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-level {
font-size: 0.75rem;
color: var(--muted-foreground);
}
.main {
flex: 1;
margin-left: 260px;
padding: 2rem;
max-width: calc(100vw - 260px);
}
.page-header {
margin-bottom: 2rem;
}
.page-title {
font-size: 1.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.page-subtitle {
color: var(--muted-foreground);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.1), rgba(59, 130, 246, 0.05));
border: 1px solid rgba(139, 92, 246, 0.2);
border-radius: 12px;
padding: 1.5rem;
transition: all 0.3s;
}
.stat-card:hover {
border-color: var(--aethex-500);
transform: translateY(-2px);
}
.stat-label {
font-size: 0.875rem;
color: var(--muted-foreground);
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: var(--aethex-400);
}
.stat-sub {
font-size: 0.75rem;
color: var(--muted-foreground);
margin-top: 0.25rem;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
margin-bottom: 1.5rem;
}
.card-header {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.card-title {
font-size: 1.125rem;
font-weight: 600;
}
.card-body { padding: 1.5rem; }
.progress-bar {
height: 8px;
background: var(--secondary);
border-radius: 4px;
overflow: hidden;
margin-top: 0.5rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--aethex-500), var(--neon-purple));
border-radius: 4px;
transition: width 0.5s ease;
}
.achievement-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
}
.achievement-item {
display: flex;
gap: 1rem;
padding: 1rem;
background: var(--secondary);
border-radius: 10px;
transition: all 0.2s;
}
.achievement-item:hover {
background: var(--card-hover);
}
.achievement-item.locked {
opacity: 0.5;
}
.achievement-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--aethex-500), var(--neon-purple));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
flex-shrink: 0;
}
.achievement-info h4 {
font-weight: 600;
margin-bottom: 0.25rem;
}
.achievement-info p {
font-size: 0.875rem;
color: var(--muted-foreground);
}
.quest-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: var(--secondary);
border-radius: 10px;
margin-bottom: 0.75rem;
}
.quest-info { flex: 1; }
.quest-name {
font-weight: 600;
margin-bottom: 0.25rem;
}
.quest-desc {
font-size: 0.875rem;
color: var(--muted-foreground);
}
.quest-progress {
text-align: right;
min-width: 100px;
}
.quest-progress-text {
font-weight: 600;
color: var(--aethex-400);
}
.shop-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1rem;
}
.shop-item {
background: var(--secondary);
border-radius: 12px;
padding: 1.25rem;
text-align: center;
transition: all 0.3s;
border: 1px solid transparent;
}
.shop-item:hover {
border-color: var(--aethex-500);
transform: translateY(-2px);
}
.shop-icon {
font-size: 2.5rem;
margin-bottom: 0.75rem;
}
.shop-name {
font-weight: 600;
margin-bottom: 0.5rem;
}
.shop-price {
color: var(--aethex-400);
font-weight: 600;
}
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 8px;
font-weight: 600;
text-decoration: none;
transition: all 0.2s;
border: none;
cursor: pointer;
font-size: 0.875rem;
}
.btn-primary {
background: linear-gradient(135deg, var(--aethex-500), var(--neon-purple));
color: white;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.4);
}
.btn-secondary {
background: var(--secondary);
color: var(--foreground);
border: 1px solid var(--border);
}
.leaderboard-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 1rem;
border-radius: 8px;
margin-bottom: 0.5rem;
background: var(--secondary);
}
.leaderboard-item.top-3 {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.15), rgba(59, 130, 246, 0.1));
border: 1px solid rgba(139, 92, 246, 0.3);
}
.leaderboard-rank {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
border-radius: 8px;
background: var(--muted);
}
.leaderboard-item.top-3 .leaderboard-rank {
background: linear-gradient(135deg, var(--aethex-500), var(--neon-purple));
}
.leaderboard-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--aethex-500);
}
.leaderboard-name { flex: 1; font-weight: 500; }
.leaderboard-xp { color: var(--aethex-400); font-weight: 600; }
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid var(--border);
padding-bottom: 0.5rem;
}
.tab {
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
color: var(--muted-foreground);
transition: all 0.2s;
}
.tab:hover { color: var(--foreground); }
.tab.active {
background: var(--aethex-500);
color: white;
}
.empty-state {
text-align: center;
padding: 3rem;
color: var(--muted-foreground);
}
.empty-state-icon {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
.login-prompt {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
text-align: center;
padding: 2rem;
}
.login-prompt h1 {
font-size: 2rem;
margin-bottom: 1rem;
}
.login-prompt p {
color: var(--muted-foreground);
margin-bottom: 2rem;
}
.hidden { display: none !important; }
.loading-spinner {
display: inline-block;
width: 24px;
height: 24px;
border: 3px solid var(--secondary);
border-top-color: var(--aethex-500);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.text-gradient {
background: linear-gradient(to right, var(--aethex-400), var(--neon-purple));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
z-index: 100;
transition: transform 0.3s;
}
.sidebar.open { transform: translateX(0); }
.main { margin-left: 0; max-width: 100%; }
}
</style>
</head>
<body>
<div class="wallpaper"></div>
<div id="loginPrompt" class="login-prompt">
<h1>Welcome to <span class="text-gradient">AeThex</span></h1>
<p>Login with Discord to view your profile, achievements, and more.</p>
<a href="/auth/discord" class="btn btn-primary">
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"/></svg>
Login with Discord
</a>
</div>
<div id="app" class="app hidden">
<aside class="sidebar">
<a href="/" class="logo">
<div class="logo-icon">A</div>
<span class="logo-text">AeThex</span>
</a>
<div class="guild-selector">
<select id="guildSelect">
<option value="">Select a server...</option>
</select>
</div>
<nav>
<div class="nav-section">
<div class="nav-section-title">Overview</div>
<div class="nav-item active" data-page="profile">
<span class="nav-icon">👤</span> Profile
</div>
<div class="nav-item" data-page="leaderboard">
<span class="nav-icon">🏆</span> Leaderboard
</div>
</div>
<div class="nav-section">
<div class="nav-section-title">Rewards</div>
<div class="nav-item" data-page="achievements">
<span class="nav-icon">🎖️</span> Achievements
</div>
<div class="nav-item" data-page="quests">
<span class="nav-icon">🎯</span> Quests
</div>
<div class="nav-item" data-page="shop">
<span class="nav-icon">🛒</span> Shop
</div>
<div class="nav-item" data-page="inventory">
<span class="nav-icon">🎒</span> Inventory
</div>
</div>
<div class="nav-section" id="adminSection" style="display:none">
<div class="nav-section-title">Admin</div>
<div class="nav-item" data-page="admin-xp">
<span class="nav-icon">⚙️</span> XP Settings
</div>
<div class="nav-item" data-page="admin-quests">
<span class="nav-icon">📝</span> Manage Quests
</div>
<div class="nav-item" data-page="admin-achievements">
<span class="nav-icon">🏅</span> Manage Achievements
</div>
<div class="nav-item" data-page="admin-shop">
<span class="nav-icon">🏪</span> Manage Shop
</div>
</div>
</nav>
<div class="user-card" id="userCard">
<img class="user-avatar" id="userAvatar" src="" alt="">
<div class="user-info">
<div class="user-name" id="userName">Loading...</div>
<div class="user-level" id="userLevel">Level 0</div>
</div>
<a href="/auth/logout" class="btn btn-secondary" style="padding:0.5rem">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></svg>
</a>
</div>
</aside>
<main class="main">
<div id="page-profile" class="page">
<div class="page-header">
<h1 class="page-title">Your <span class="text-gradient">Profile</span></h1>
<p class="page-subtitle">Track your progress and stats</p>
</div>
<div class="stats-grid" id="statsGrid">
<div class="stat-card">
<div class="stat-label">Total XP</div>
<div class="stat-value" id="statXp">0</div>
<div class="stat-sub" id="statXpProgress">0 XP to next level</div>
</div>
<div class="stat-card">
<div class="stat-label">Level</div>
<div class="stat-value" id="statLevel">0</div>
<div class="stat-sub" id="statPrestige">Prestige 0</div>
</div>
<div class="stat-card">
<div class="stat-label">Messages</div>
<div class="stat-value" id="statMessages">0</div>
<div class="stat-sub">All time</div>
</div>
<div class="stat-card">
<div class="stat-label">Voice Time</div>
<div class="stat-value" id="statVoice">0h</div>
<div class="stat-sub">All time</div>
</div>
<div class="stat-card">
<div class="stat-label">Daily Streak</div>
<div class="stat-value" id="statStreak">0</div>
<div class="stat-sub">days</div>
</div>
<div class="stat-card">
<div class="stat-label">Achievements</div>
<div class="stat-value" id="statAchievements">0</div>
<div class="stat-sub">earned</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title">Level Progress</h3>
</div>
<div class="card-body">
<div style="display:flex;justify-content:space-between;margin-bottom:0.5rem">
<span>Level <span id="currentLevel">0</span></span>
<span>Level <span id="nextLevel">1</span></span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="levelProgress" style="width:0%"></div>
</div>
<p style="margin-top:0.75rem;color:var(--muted-foreground);font-size:0.875rem">
<span id="xpCurrent">0</span> / <span id="xpRequired">100</span> XP
</p>
</div>
</div>
</div>
<div id="page-leaderboard" class="page hidden">
<div class="page-header">
<h1 class="page-title">Leaderboard</h1>
<p class="page-subtitle">See who's on top</p>
</div>
<div class="tabs">
<div class="tab active" data-lb-type="all">All Time</div>
<div class="tab" data-lb-type="monthly">Monthly</div>
<div class="tab" data-lb-type="weekly">Weekly</div>
</div>
<div class="card">
<div class="card-body" id="leaderboardList">
<div class="empty-state">
<div class="empty-state-icon">🏆</div>
<p>Select a server to view the leaderboard</p>
</div>
</div>
</div>
</div>
<div id="page-achievements" class="page hidden">
<div class="page-header">
<h1 class="page-title">Achievements</h1>
<p class="page-subtitle">Collect badges and show off your accomplishments</p>
</div>
<div class="achievement-grid" id="achievementGrid">
<div class="empty-state" style="grid-column:1/-1">
<div class="empty-state-icon">🎖️</div>
<p>Select a server to view achievements</p>
</div>
</div>
</div>
<div id="page-quests" class="page hidden">
<div class="page-header">
<h1 class="page-title">Quests</h1>
<p class="page-subtitle">Complete challenges to earn rewards</p>
</div>
<div class="card">
<div class="card-body" id="questList">
<div class="empty-state">
<div class="empty-state-icon">🎯</div>
<p>Select a server to view quests</p>
</div>
</div>
</div>
</div>
<div id="page-shop" class="page hidden">
<div class="page-header">
<h1 class="page-title">Shop</h1>
<p class="page-subtitle">Spend your XP on rewards</p>
</div>
<div class="shop-grid" id="shopGrid">
<div class="empty-state" style="grid-column:1/-1">
<div class="empty-state-icon">🛒</div>
<p>Select a server to view shop items</p>
</div>
</div>
</div>
<div id="page-inventory" class="page hidden">
<div class="page-header">
<h1 class="page-title">Inventory</h1>
<p class="page-subtitle">Your purchased items and perks</p>
</div>
<div class="shop-grid" id="inventoryGrid">
<div class="empty-state" style="grid-column:1/-1">
<div class="empty-state-icon">🎒</div>
<p>Your inventory is empty</p>
</div>
</div>
</div>
<div id="page-admin-xp" class="page hidden">
<div class="page-header">
<h1 class="page-title">XP Settings</h1>
<p class="page-subtitle">Configure XP earning for your server</p>
</div>
<div class="card">
<div class="card-body">
<p style="color:var(--muted-foreground)">Use <code>/xp-settings</code> in Discord to configure XP settings. Web-based configuration coming soon!</p>
</div>
</div>
</div>
<div id="page-admin-quests" class="page hidden">
<div class="page-header">
<h1 class="page-title">Manage Quests</h1>
<p class="page-subtitle">Create and edit server quests</p>
</div>
<div class="card">
<div class="card-body">
<p style="color:var(--muted-foreground)">Use <code>/quests-manage</code> in Discord to manage quests. Web-based management coming soon!</p>
</div>
</div>
</div>
<div id="page-admin-achievements" class="page hidden">
<div class="page-header">
<h1 class="page-title">Manage Achievements</h1>
<p class="page-subtitle">Create custom achievements</p>
</div>
<div class="card">
<div class="card-body">
<p style="color:var(--muted-foreground)">Use <code>/achievements-manage</code> in Discord to manage achievements. Web-based management coming soon!</p>
</div>
</div>
</div>
<div id="page-admin-shop" class="page hidden">
<div class="page-header">
<h1 class="page-title">Manage Shop</h1>
<p class="page-subtitle">Configure shop items and prices</p>
</div>
<div class="card">
<div class="card-body">
<p style="color:var(--muted-foreground)">Use <code>/shop-manage</code> in Discord to manage shop items. Web-based management coming soon!</p>
</div>
</div>
</div>
</main>
</div>
<script>
let currentUser = null;
let currentGuild = null;
let currentPage = 'profile';
async function init() {
try {
const res = await fetch('/api/me');
if (!res.ok) {
document.getElementById('loginPrompt').classList.remove('hidden');
return;
}
currentUser = await res.json();
document.getElementById('loginPrompt').classList.add('hidden');
document.getElementById('app').classList.remove('hidden');
document.getElementById('userAvatar').src = currentUser.avatarUrl;
document.getElementById('userName').textContent = currentUser.globalName || currentUser.username;
const select = document.getElementById('guildSelect');
currentUser.guilds.forEach(g => {
const opt = document.createElement('option');
opt.value = g.id;
opt.textContent = g.name;
opt.dataset.isAdmin = g.isAdmin;
select.appendChild(opt);
});
select.addEventListener('change', () => {
currentGuild = select.value;
const isAdmin = select.selectedOptions[0]?.dataset.isAdmin === 'true';
document.getElementById('adminSection').style.display = isAdmin ? 'block' : 'none';
loadPageData();
});
if (currentUser.guilds.length > 0) {
select.value = currentUser.guilds[0].id;
select.dispatchEvent(new Event('change'));
}
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', () => {
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
currentPage = item.dataset.page;
showPage(currentPage);
});
});
document.querySelectorAll('[data-lb-type]').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('[data-lb-type]').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
loadLeaderboard(tab.dataset.lbType);
});
});
await loadProfile();
} catch (e) {
console.error('Init error:', e);
document.getElementById('loginPrompt').classList.remove('hidden');
}
}
function showPage(page) {
document.querySelectorAll('.page').forEach(p => p.classList.add('hidden'));
document.getElementById('page-' + page)?.classList.remove('hidden');
loadPageData();
}
async function loadPageData() {
if (!currentGuild) return;
switch (currentPage) {
case 'profile': await loadProfile(); break;
case 'leaderboard': await loadLeaderboard('all'); break;
case 'achievements': await loadAchievements(); break;
case 'quests': await loadQuests(); break;
case 'shop': await loadShop(); break;
case 'inventory': await loadInventory(); break;
}
}
async function loadProfile() {
if (!currentUser) return;
try {
const [profileRes, statsRes] = await Promise.all([
fetch('/api/profile/' + currentUser.id),
currentGuild ? fetch('/api/stats/' + currentUser.id + '/' + currentGuild) : null
]);
const profileData = await profileRes.json();
const statsData = statsRes ? await statsRes.json() : { stats: null };
if (profileData.linked && profileData.profile) {
const p = profileData.profile;
const xp = p.xp || 0;
const level = Math.floor(Math.sqrt(xp / 100));
const nextLevel = level + 1;
const currentLevelXp = level * level * 100;
const nextLevelXp = nextLevel * nextLevel * 100;
const progress = ((xp - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100;
document.getElementById('statXp').textContent = xp.toLocaleString();
document.getElementById('statLevel').textContent = level;
document.getElementById('statPrestige').textContent = 'Prestige ' + (p.prestigeLevel || 0);
document.getElementById('statStreak').textContent = p.dailyStreak || 0;
document.getElementById('userLevel').textContent = 'Level ' + level;
document.getElementById('currentLevel').textContent = level;
document.getElementById('nextLevel').textContent = nextLevel;
document.getElementById('xpCurrent').textContent = (xp - currentLevelXp).toLocaleString();
document.getElementById('xpRequired').textContent = (nextLevelXp - currentLevelXp).toLocaleString();
document.getElementById('levelProgress').style.width = progress + '%';
}
if (statsData.stats) {
const s = statsData.stats;
document.getElementById('statMessages').textContent = (s.messages_sent || 0).toLocaleString();
const voiceMinutes = s.voice_minutes || 0;
document.getElementById('statVoice').textContent = Math.floor(voiceMinutes / 60) + 'h';
}
} catch (e) {
console.error('Profile load error:', e);
}
}
async function loadLeaderboard(type) {
if (!currentGuild) return;
const container = document.getElementById('leaderboardList');
container.innerHTML = '<div class="loading-spinner"></div>';
try {
const res = await fetch('/api/leaderboard/' + currentGuild + '?type=' + type + '&limit=25');
const data = await res.json();
if (!data.leaderboard || data.leaderboard.length === 0) {
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🏆</div><p>No leaderboard data yet</p></div>';
return;
}
container.innerHTML = data.leaderboard.map((entry, i) => `
<div class="leaderboard-item ${i < 3 ? 'top-3' : ''}">
<div class="leaderboard-rank">${i + 1}</div>
<img class="leaderboard-avatar" src="${entry.user_profiles?.avatar_url || ''}" alt="">
<div class="leaderboard-name">${entry.user_profiles?.username || 'Unknown'}</div>
<div class="leaderboard-xp">${(entry.xp || 0).toLocaleString()} XP</div>
</div>
`).join('');
} catch (e) {
container.innerHTML = '<div class="empty-state"><p>Failed to load leaderboard</p></div>';
}
}
async function loadAchievements() {
if (!currentGuild) return;
const container = document.getElementById('achievementGrid');
container.innerHTML = '<div class="loading-spinner"></div>';
try {
const res = await fetch('/api/achievements/' + currentGuild);
const data = await res.json();
if (!data.achievements || data.achievements.length === 0) {
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><div class="empty-state-icon">🎖️</div><p>No achievements configured for this server</p></div>';
return;
}
const earnedIds = new Set(data.userAchievements?.map(a => a.achievement_id) || []);
document.getElementById('statAchievements').textContent = earnedIds.size;
container.innerHTML = data.achievements.map(a => `
<div class="achievement-item ${earnedIds.has(a.id) ? '' : 'locked'}">
<div class="achievement-icon">${a.icon || '🏅'}</div>
<div class="achievement-info">
<h4>${a.name}</h4>
<p>${a.description || ''}</p>
</div>
</div>
`).join('');
} catch (e) {
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><p>Failed to load achievements</p></div>';
}
}
async function loadQuests() {
if (!currentGuild) return;
const container = document.getElementById('questList');
container.innerHTML = '<div class="loading-spinner"></div>';
try {
const res = await fetch('/api/quests/' + currentGuild);
const data = await res.json();
if (!data.quests || data.quests.length === 0) {
container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🎯</div><p>No active quests</p></div>';
return;
}
const userQuestMap = {};
(data.userQuests || []).forEach(uq => userQuestMap[uq.quest_id] = uq);
container.innerHTML = data.quests.map(q => {
const uq = userQuestMap[q.id];
const progress = uq?.progress || 0;
const pct = Math.min(100, (progress / q.target_value) * 100);
return `
<div class="quest-item">
<div class="quest-info">
<div class="quest-name">${q.name}</div>
<div class="quest-desc">${q.description || ''}</div>
<div class="progress-bar" style="margin-top:0.5rem">
<div class="progress-fill" style="width:${pct}%"></div>
</div>
</div>
<div class="quest-progress">
<div class="quest-progress-text">${progress}/${q.target_value}</div>
<div style="font-size:0.75rem;color:var(--muted-foreground)">${q.xp_reward} XP</div>
</div>
</div>
`;
}).join('');
} catch (e) {
container.innerHTML = '<div class="empty-state"><p>Failed to load quests</p></div>';
}
}
async function loadShop() {
if (!currentGuild) return;
const container = document.getElementById('shopGrid');
container.innerHTML = '<div class="loading-spinner"></div>';
try {
const res = await fetch('/api/shop/' + currentGuild);
const data = await res.json();
if (!data.items || data.items.length === 0) {
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><div class="empty-state-icon">🛒</div><p>No shop items available</p></div>';
return;
}
const icons = { title: '📛', badge: '🏅', color: '🎨', role: '👑', xp_boost: '⚡', custom: '✨' };
container.innerHTML = data.items.map(item => `
<div class="shop-item">
<div class="shop-icon">${icons[item.item_type] || '🎁'}</div>
<div class="shop-name">${item.name}</div>
<div class="shop-price">${item.price.toLocaleString()} XP</div>
</div>
`).join('');
} catch (e) {
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><p>Failed to load shop</p></div>';
}
}
async function loadInventory() {
if (!currentGuild) return;
const container = document.getElementById('inventoryGrid');
container.innerHTML = '<div class="loading-spinner"></div>';
try {
const res = await fetch('/api/inventory/' + currentGuild);
const data = await res.json();
if (!data.inventory || data.inventory.length === 0) {
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><div class="empty-state-icon">🎒</div><p>Your inventory is empty</p></div>';
return;
}
const icons = { title: '📛', badge: '🏅', color: '🎨', role: '👑', xp_boost: '⚡', custom: '✨' };
container.innerHTML = data.inventory.map(item => `
<div class="shop-item ${item.equipped ? 'equipped' : ''}">
<div class="shop-icon">${icons[item.shop_items?.item_type] || '🎁'}</div>
<div class="shop-name">${item.shop_items?.name || 'Unknown Item'}</div>
<div style="font-size:0.75rem;color:var(--muted-foreground)">${item.equipped ? 'Equipped' : ''}</div>
</div>
`).join('');
} catch (e) {
container.innerHTML = '<div class="empty-state" style="grid-column:1/-1"><p>Failed to load inventory</p></div>';
}
}
init();
</script>
</body>
</html>