Really-amin's picture
Upload 577 files
b190b45 verified
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Crypto Intelligence Hub - Modern Dashboard with 40+ Data Sources">
<title>Dashboard | Crypto Intelligence Hub</title>
<!-- Favicon -->
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2322d3ee'/%3E%3Cstop offset='100%25' stop-color='%236366f1'/%3E%3C/linearGradient%3E%3C/defs%3E%3Ccircle cx='50' cy='50' r='45' fill='url(%23g)'/%3E%3C/svg%3E">
<!-- Modern Theme -->
<link rel="stylesheet" href="/static/shared/css/theme-modern.css">
<link rel="stylesheet" href="/static/shared/css/sidebar-modern.css">
<style>
/* Page-specific styles */
body {
margin: 0;
padding: 0;
background: var(--bg-secondary);
}
.app-layout {
display: flex;
min-height: 100vh;
}
.main-content {
flex: 1;
margin-left: var(--sidebar-width);
transition: margin-left var(--transition-base);
padding: var(--space-6);
}
.sidebar-modern.collapsed ~ .main-content {
margin-left: var(--sidebar-collapsed-width);
}
/* Header */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-8);
padding-bottom: var(--space-6);
border-bottom: 1px solid var(--border-primary);
}
.page-title h1 {
font-size: var(--text-4xl);
font-weight: var(--font-bold);
background: var(--accent-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: var(--space-2);
}
.page-subtitle {
color: var(--text-tertiary);
font-size: var(--text-lg);
}
.page-actions {
display: flex;
gap: var(--space-3);
}
.btn {
padding: var(--space-3) var(--space-6);
border-radius: var(--radius-lg);
font-weight: var(--font-semibold);
font-size: var(--text-sm);
cursor: pointer;
transition: all var(--transition-base);
border: none;
display: inline-flex;
align-items: center;
gap: var(--space-2);
}
.btn-primary {
background: var(--accent-gradient);
color: white;
box-shadow: var(--shadow-md);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.btn-secondary {
background: var(--surface-secondary);
color: var(--text-primary);
border: 1px solid var(--border-primary);
}
.btn-secondary:hover {
background: var(--surface-hover);
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--space-6);
margin-bottom: var(--space-8);
}
.stat-card {
background: var(--surface-primary);
border-radius: var(--radius-xl);
padding: var(--space-6);
border: 1px solid var(--border-primary);
box-shadow: var(--shadow-sm);
transition: all var(--transition-base);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--accent-gradient);
opacity: 0;
transition: opacity var(--transition-fast);
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-md);
}
.stat-card:hover::before {
opacity: 1;
}
.stat-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-4);
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: var(--radius-lg);
background: var(--accent-gradient);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(34, 211, 238, 0.3);
}
.stat-icon svg {
width: 24px;
height: 24px;
color: white;
}
.stat-badge {
padding: var(--space-1) var(--space-3);
border-radius: var(--radius-full);
font-size: var(--text-xs);
font-weight: var(--font-bold);
text-transform: uppercase;
}
.stat-badge.success {
background: rgba(16, 185, 129, 0.1);
color: var(--color-success);
}
.stat-badge.warning {
background: rgba(245, 158, 11, 0.1);
color: var(--color-warning);
}
.stat-value {
font-size: var(--text-4xl);
font-weight: var(--font-extrabold);
color: var(--text-primary);
margin-bottom: var(--space-2);
}
.stat-label {
font-size: var(--text-sm);
color: var(--text-tertiary);
font-weight: var(--font-medium);
}
.stat-change {
margin-top: var(--space-3);
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--text-sm);
font-weight: var(--font-semibold);
}
.stat-change.positive {
color: var(--color-success);
}
.stat-change.negative {
color: var(--color-danger);
}
/* Cards */
.card {
background: var(--surface-primary);
border-radius: var(--radius-xl);
border: 1px solid var(--border-primary);
box-shadow: var(--shadow-sm);
overflow: hidden;
margin-bottom: var(--space-6);
}
.card-header {
padding: var(--space-6);
border-bottom: 1px solid var(--border-primary);
display: flex;
align-items: center;
justify-content: space-between;
}
.card-title {
font-size: var(--text-xl);
font-weight: var(--font-bold);
color: var(--text-primary);
display: flex;
align-items: center;
gap: var(--space-3);
}
.card-body {
padding: var(--space-6);
}
/* Loading */
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-8);
color: var(--text-tertiary);
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--border-primary);
border-top-color: var(--accent-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* News List */
.news-list {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.news-item {
display: flex;
gap: var(--space-4);
padding: var(--space-4);
border-radius: var(--radius-lg);
transition: all var(--transition-fast);
cursor: pointer;
}
.news-item:hover {
background: var(--surface-hover);
}
.news-source {
font-size: var(--text-xs);
color: var(--text-tertiary);
font-weight: var(--font-semibold);
text-transform: uppercase;
}
.news-title {
font-size: var(--text-base);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin: var(--space-2) 0;
}
.news-time {
font-size: var(--text-sm);
color: var(--text-tertiary);
}
/* Fear & Greed */
.fng-gauge {
width: 200px;
height: 200px;
margin: 0 auto;
position: relative;
}
.fng-circle {
width: 100%;
height: 100%;
border-radius: 50%;
background: conic-gradient(
from 180deg,
var(--color-danger) 0deg 45deg,
var(--color-warning) 45deg 90deg,
var(--color-info) 90deg 135deg,
var(--color-success) 135deg 180deg
);
display: flex;
align-items: center;
justify-content: center;
}
.fng-inner {
width: 80%;
height: 80%;
background: var(--bg-primary);
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.fng-value {
font-size: var(--text-5xl);
font-weight: var(--font-extrabold);
background: var(--accent-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.fng-label {
font-size: var(--text-sm);
color: var(--text-tertiary);
font-weight: var(--font-semibold);
margin-top: var(--space-2);
}
/* Responsive */
@media (max-width: 1024px) {
.main-content {
margin-left: 0;
}
.sidebar-modern.collapsed ~ .main-content {
margin-left: 0;
}
}
@media (max-width: 768px) {
.main-content {
padding: var(--space-4);
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: var(--space-4);
}
.stats-grid {
grid-template-columns: 1fr;
}
}
</style>
<!-- API Configuration - Smart Fallback System -->
<script src="/static/js/api-config.js"></script>
<script>
// Initialize API client
window.apiReady = new Promise((resolve) => {
if (window.apiClient) {
console.log('✅ API Client ready');
resolve(window.apiClient);
} else {
console.error('❌ API Client not loaded');
}
});
</script>
</head>
<body>
<div class="app-layout">
<!-- Sidebar Container -->
<div id="sidebar-container"></div>
<!-- Main Content -->
<main class="main-content">
<!-- Page Header -->
<header class="page-header">
<div class="page-title">
<h1>Dashboard</h1>
<p class="page-subtitle">Real-time crypto market intelligence</p>
</div>
<div class="page-actions">
<button class="btn btn-secondary" id="refresh-btn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
<path d="M21 3v5h-5"/>
</svg>
Refresh
</button>
<button class="btn btn-primary" id="theme-toggle">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="4"/>
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/>
</svg>
Toggle Theme
</button>
</div>
</header>
<!-- Stats Grid -->
<div class="stats-grid">
<!-- BTC Price -->
<div class="stat-card" id="btc-card">
<div class="stat-header">
<div class="stat-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
</svg>
</div>
<span class="stat-badge success">Live</span>
</div>
<div class="stat-value" id="btc-price">Loading...</div>
<div class="stat-label">Bitcoin (BTC)</div>
<div class="stat-change positive" id="btc-change">
<span></span>
<span>--</span>
</div>
</div>
<!-- ETH Price -->
<div class="stat-card" id="eth-card">
<div class="stat-header">
<div class="stat-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="12 2 3 12 12 16 21 12"/>
<polygon points="12 22 3 14 12 18 21 14"/>
</svg>
</div>
<span class="stat-badge success">Live</span>
</div>
<div class="stat-value" id="eth-price">Loading...</div>
<div class="stat-label">Ethereum (ETH)</div>
<div class="stat-change positive" id="eth-change">
<span></span>
<span>--</span>
</div>
</div>
<!-- Market Cap -->
<div class="stat-card">
<div class="stat-header">
<div class="stat-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 3v18h18"/>
<path d="M18 7l-5 5-4-4-5 5"/>
</svg>
</div>
<span class="stat-badge warning">24h</span>
</div>
<div class="stat-value" id="total-cap">$2.1T</div>
<div class="stat-label">Total Market Cap</div>
<div class="stat-change positive">
<span></span>
<span>2.3%</span>
</div>
</div>
<!-- API Status -->
<div class="stat-card">
<div class="stat-header">
<div class="stat-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 11a9 9 0 0 1 9 9"/>
<path d="M4 4a16 16 0 0 1 16 16"/>
<circle cx="5" cy="19" r="2"/>
</svg>
</div>
<span class="stat-badge success" id="api-status-badge">Online</span>
</div>
<div class="stat-value" id="api-success-rate">98%</div>
<div class="stat-label">API Success Rate</div>
<div class="stat-change positive" id="api-stats">
<span>40+</span>
<span>sources active</span>
</div>
</div>
</div>
<!-- Grid Layout -->
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: var(--space-6);">
<!-- News Card -->
<div class="card">
<div class="card-header">
<div class="card-title">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"/>
</svg>
Latest News
</div>
<span id="news-count" style="color: var(--text-tertiary); font-size: var(--text-sm);">Loading...</span>
</div>
<div class="card-body">
<div class="news-list" id="news-list">
<div class="loading">
<div class="spinner"></div>
</div>
</div>
</div>
</div>
<!-- Fear & Greed -->
<div class="card">
<div class="card-header">
<div class="card-title">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<path d="M8 14s1.5 2 4 2 4-2 4-2"/>
</svg>
Fear & Greed
</div>
</div>
<div class="card-body">
<div class="fng-gauge" id="fng-gauge">
<div class="fng-circle">
<div class="fng-inner">
<div class="fng-value" id="fng-value">--</div>
<div class="fng-label" id="fng-label">Loading...</div>
</div>
</div>
</div>
<div style="text-align: center; margin-top: var(--space-6); color: var(--text-tertiary); font-size: var(--text-sm);" id="fng-source">
Source: --
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Load Scripts -->
<script type="module">
import apiClient from '/static/shared/js/api-client-comprehensive.js';
import sidebarManager from '/static/shared/js/sidebar-manager.js';
// Load sidebar HTML
fetch('/static/shared/layouts/sidebar-modern.html')
.then(r => r.text())
.then(html => {
document.getElementById('sidebar-container').innerHTML = html;
});
// Theme toggle
document.getElementById('theme-toggle').addEventListener('click', () => {
const html = document.documentElement;
const current = html.getAttribute('data-theme') || 'light';
const next = current === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
// Load saved theme
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
// Load dashboard data
async function loadDashboard() {
try {
// Load BTC price
const btc = await apiClient.getMarketPrice('bitcoin');
document.getElementById('btc-price').textContent = `$${btc.price.toLocaleString()}`;
if (btc.change24h) {
const changeEl = document.getElementById('btc-change');
changeEl.innerHTML = `<span>${btc.change24h > 0 ? '↑' : '↓'}</span><span>${Math.abs(btc.change24h).toFixed(2)}%</span>`;
changeEl.className = `stat-change ${btc.change24h > 0 ? 'positive' : 'negative'}`;
}
// Load ETH price
const eth = await apiClient.getMarketPrice('ethereum');
document.getElementById('eth-price').textContent = `$${eth.price.toLocaleString()}`;
if (eth.change24h) {
const changeEl = document.getElementById('eth-change');
changeEl.innerHTML = `<span>${eth.change24h > 0 ? '↑' : '↓'}</span><span>${Math.abs(eth.change24h).toFixed(2)}%</span>`;
changeEl.className = `stat-change ${eth.change24h > 0 ? 'positive' : 'negative'}`;
}
// Load Fear & Greed
const fng = await apiClient.getSentiment();
document.getElementById('fng-value').textContent = fng.value;
document.getElementById('fng-label').textContent = fng.classification;
document.getElementById('fng-source').textContent = `Source: ${fng.source}`;
// Load News
const news = await apiClient.getNews(10);
document.getElementById('news-count').textContent = `${news.length} articles`;
const newsList = document.getElementById('news-list');
newsList.innerHTML = news.map(item => `
<div class="news-item" onclick="window.open('${item.link}', '_blank')">
<div style="flex: 1;">
<div class="news-source">${item.source}</div>
<div class="news-title">${item.title}</div>
<div class="news-time">${new Date(item.publishedAt || Date.now()).toLocaleString()}</div>
</div>
</div>
`).join('');
// Update API stats
const stats = apiClient.getStats();
document.getElementById('api-success-rate').textContent = stats.successRate;
document.getElementById('api-stats').innerHTML = `<span>${stats.successful}/${stats.total}</span><span>requests</span>`;
console.log('✅ Dashboard loaded successfully');
console.log('API Stats:', stats);
} catch (error) {
console.error('❌ Failed to load dashboard:', error);
}
}
// Refresh handler
document.getElementById('refresh-btn').addEventListener('click', () => {
apiClient.clearCache();
loadDashboard();
});
// Initial load
loadDashboard();
// Auto-refresh every 2 minutes
setInterval(() => {
loadDashboard();
}, 120000);
</script>
</body>
</html>