mk068's picture
improve the design fix ux properliy
0113d8d verified
// CryptoVista Dashboard Pro - Enhanced JavaScript
// API Configuration with fallbacks
const COINGECKO_API = 'https://api.coingecko.com/api/v3';
const CRYPTO_NEWS_API = 'https://min-api.cryptocompare.com/data/v2/news/';
const ALTERNATIVE_API = 'https://api.coincap.io/v2/assets?limit=5';
// Global State with enhanced monitoring
let cryptoData = {
marketData: null,
topGainers: [],
news: [],
userPreferences: {
currency: 'usd',
theme: 'dark',
refreshInterval: 30000,
notifications: true
},
performance: {
lastUpdate: null,
apiLatency: {},
errors: []
}
};
let selectedCurrency = localStorage.getItem('preferredCurrency') || 'usd';
let theme = localStorage.getItem('theme') || 'dark';
let isInitialized = false;
// Performance monitoring
const perfMonitor = {
start: (label) => {
if (window.performance) {
perfMonitor[label] = performance.now();
}
},
end: (label) => {
if (window.performance && perfMonitor[label]) {
const duration = performance.now() - perfMonitor[label];
console.log(`⏱️ ${label}: ${duration.toFixed(2)}ms`);
return duration;
}
return 0;
}
};
// Initialize Dashboard with enhanced UX
async function initializeDashboard() {
if (isInitialized) {
console.warn('Dashboard already initialized');
return;
}
perfMonitor.start('init');
try {
console.log('πŸš€ Initializing CryptoVista Dashboard Pro...');
// Set initial theme
applyTheme(theme);
// Show loading state
showLoadingState();
// Load critical data first
await Promise.allSettled([
loadTopGainers(),
loadNews(),
loadMarketData()
]);
// Setup enhanced auto-refresh
setupAutoRefresh();
// Setup enhanced event listeners
setupEventListeners();
// Initialize UI components
initializeUIComponents();
// Mark as initialized
isInitialized = true;
// Hide loading state
hideLoadingState();
perfMonitor.end('init');
// Show welcome message
setTimeout(() => {
showToast('Dashboard loaded successfully!', 'success');
}, 500);
console.log('βœ… Dashboard initialized successfully');
} catch (error) {
console.error('❌ Dashboard initialization failed:', error);
showError('global', 'Failed to initialize dashboard. Please refresh.');
hideLoadingState();
}
}
// Load Top Gainers from CoinGecko API
async function loadTopGainers() {
try {
const response = await fetch(`${COINGECKO_API}/coins/markets?vs_currency=${selectedCurrency}&order=market_cap_desc&per_page=5&sparkline=false&price_change_percentage=24h`);
const gainers = await response.json();
const container = document.getElementById('topGainers');
container.innerHTML = '';
gainers.forEach((coin, index) => {
const isPositive = coin.price_change_percentage_24h > 0;
const changeClass = isPositive ? 'positive' : 'negative';
const coinElement = `
<div class="flex items-center justify-between p-4 bg-dark-900 rounded-xl hover:bg-dark-700 transition-colors cursor-pointer"
onclick="window.location.href='/coin/${coin.id}'">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-gradient-to-r from-primary-600 to-secondary-600 flex items-center justify-center">
<span class="font-bold">${coin.symbol.toUpperCase()}</span>
</div>
<div>
<p class="font-medium">${coin.name}</p>
<p class="text-sm text-gray-400">$${coin.current_price.toLocaleString()}</p>
</div>
</div>
<div class="text-right">
<span class="price-change ${changeClass}">
${isPositive ? '+' : ''}${coin.price_change_percentage_24h?.toFixed(2) || 0}%
</span>
</div>
</div>
`;
container.innerHTML += coinElement;
});
} catch (error) {
console.error('Error loading top gainers:', error);
showError('topGainers', 'Failed to load top gainers data');
}
}
// Load Crypto News from CryptoCompare API
async function loadNews() {
try {
// Note: In production, you'd use your actual API key
const response = await fetch(CRYPTO_NEWS_API);
const data = await response.json();
const news = data.Data?.slice(0, 3) || [];
const container = document.getElementById('newsFeed');
container.innerHTML = '';
news.forEach((article, index) => {
const imageUrl = article.imageurl || `http://static.photos/technology/640x360/${index + 1}`;
const newsElement = `
<a href="${article.url}" target="_blank" rel="noopener noreferrer"
class="block bg-dark-900 rounded-xl overflow-hidden hover-lift group">
<div class="relative overflow-hidden">
<img src="${imageUrl}"
alt="${article.title}"
class="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300">
<div class="absolute inset-0 bg-gradient-to-t from-dark-900/80 to-transparent"></div>
</div>
<div class="p-4">
<h4 class="font-semibold mb-2 line-clamp-2 group-hover:text-primary-400 transition-colors">
${article.title}
</h4>
<div class="flex items-center justify-between text-sm text-gray-400">
<span>${article.source}</span>
<span>${formatTimeAgo(article.published_on)}</span>
</div>
</div>
</a>
`;
container.innerHTML += newsElement;
});
} catch (error) {
console.error('Error loading news:', error);
showError('newsFeed', 'Failed to load news feed');
// Fallback to static news
const container = document.getElementById('newsFeed');
container.innerHTML = `
<div class="col-span-3 text-center py-12">
<i data-feather="wifi-off" class="w-12 h-12 text-gray-600 mx-auto mb-4"></i>
<p class="text-gray-400">News feed temporarily unavailable</p>
<p class="text-sm text-gray-500">Check back soon for updates</p>
</div>
`;
feather.replace();
}
}
// Load Market Overview Data
async function loadMarketData() {
try {
const response = await fetch(`${COINGECKO_API}/global`);
const data = await response.json();
cryptoData.marketData = data.data;
// Update market stats
updateMarketStats(data.data);
} catch (error) {
console.error('Error loading market data:', error);
}
}
// Update Market Stats Display
function updateMarketStats(marketData) {
const elements = {
totalMarketCap: document.querySelector('[data-market-cap]'),
totalVolume: document.querySelector('[data-volume]'),
btcDominance: document.querySelector('[data-btc-dominance]'),
activeCryptos: document.querySelector('[data-active-cryptos]')
};
if (marketData && marketData.total_market_cap) {
Object.keys(elements).forEach(key => {
if (elements[key] && marketData[key]) {
const value = marketData[key];
let formattedValue;
switch(key) {
case 'total_market_cap':
case 'total_volume':
formattedValue = `$${(value.usd / 1e12).toFixed(2)}T`;
break;
case 'btc_dominance':
formattedValue = `${value.toFixed(2)}%`;
break;
case 'active_cryptocurrencies':
formattedValue = value.toLocaleString();
break;
default:
formattedValue = value;
}
elements[key].textContent = formattedValue;
}
});
}
}
// Setup Auto Refresh
function setupAutoRefresh() {
// Refresh specific components at different intervals
setInterval(() => {
loadTopGainers();
console.log('πŸ”„ Refreshed top gainers');
}, 30000); // 30 seconds
setInterval(() => {
loadMarketData();
console.log('πŸ“Š Refreshed market data');
}, 60000); // 1 minute
setInterval(() => {
loadNews();
console.log('πŸ“° Refreshed news feed');
}, 120000); // 2 minutes
}
// Setup Event Listeners
function setupEventListeners() {
// Currency selector
const currencyButtons = document.querySelectorAll('[data-currency]');
currencyButtons.forEach(button => {
button.addEventListener('click', () => {
selectedCurrency = button.dataset.currency;
currencyButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
loadTopGainers();
});
});
// Theme toggle
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
themeToggle.addEventListener('click', toggleTheme);
}
// Search functionality
const searchInput = document.getElementById('cryptoSearch');
if (searchInput) {
searchInput.addEventListener('input', debounce(handleSearch, 300));
}
// Time range buttons
const timeRangeButtons = document.querySelectorAll('[data-time-range]');
timeRangeButtons.forEach(button => {
button.addEventListener('click', () => {
timeRangeButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// In production, update chart time range here
});
});
// Mobile menu toggle
const mobileMenuButton = document.getElementById('mobileMenuButton');
const mobileMenu = document.getElementById('mobileMenu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
}
}
// Handle Search Functionality
async function handleSearch(event) {
const query = event.target.value.toLowerCase().trim();
if (query.length < 2) {
hideSearchResults();
return;
}
try {
const response = await fetch(`${COINGECKO_API}/search?query=${query}`);
const data = await response.json();
displaySearchResults(data.coins || []);
} catch (error) {
console.error('Search error:', error);
}
}
// Display Search Results
function displaySearchResults(coins) {
const container = document.getElementById('searchResults');
if (!container) return;
container.innerHTML = coins.slice(0, 5).map(coin => `
<a href="/coin/${coin.id}" class="flex items-center gap-3 p-3 hover:bg-dark-700 rounded-lg transition-colors">
<img src="${coin.thumb}" alt="${coin.name}" class="w-8 h-8 rounded-full">
<div>
<p class="font-medium">${coin.name}</p>
<p class="text-sm text-gray-400">${coin.symbol.toUpperCase()}</p>
</div>
</a>
`).join('');
container.classList.remove('hidden');
}
// Hide Search Results
function hideSearchResults() {
const container = document.getElementById('searchResults');
if (container) {
container.classList.add('hidden');
}
}
// Toggle Theme
function toggleTheme() {
const html = document.documentElement;
if (theme === 'dark') {
theme = 'light';
html.classList.remove('dark');
html.classList.add('light');
} else {
theme = 'dark';
html.classList.remove('light');
html.classList.add('dark');
}
// Save preference
localStorage.setItem('theme', theme);
// Update UI
updateThemeUI();
}
// Update Theme UI
function updateThemeUI() {
const themeToggle = document.getElementById('themeToggle');
if (!themeToggle) return;
const icon = themeToggle.querySelector('i[data-feather]');
if (icon) {
const newIcon = theme === 'dark' ? 'sun' : 'moon';
icon.setAttribute('data-feather', newIcon);
feather.replace();
}
}
// Show Error Message
function showError(elementId, message) {
const element = document.getElementById(elementId);
if (element) {
element.innerHTML = `
<div class="text-center py-8">
<i data-feather="alert-circle" class="w-12 h-12 text-red-500 mx-auto mb-4"></i>
<p class="text-gray-400">${message}</p>
</div>
`;
feather.replace();
}
}
// Format Time Ago
function formatTimeAgo(timestamp) {
const seconds = Math.floor((Date.now() - timestamp * 1000) / 1000);
const intervals = [
{ label: 'year', seconds: 31536000 },
{ label: 'month', seconds: 2592000 },
{ label: 'day', seconds: 86400 },
{ label: 'hour', seconds: 3600 },
{ label: 'minute', seconds: 60 },
{ label: 'second', seconds: 1 }
];
for (const interval of intervals) {
const count = Math.floor(seconds / interval.seconds);
if (count >= 1) {
return `${count} ${interval.label}${count !== 1 ? 's' : ''} ago`;
}
}
return 'just now';
}
// Debounce Function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Load saved preferences
function loadPreferences() {
const savedTheme = localStorage.getItem('theme');
const savedCurrency = localStorage.getItem('currency');
if (savedTheme) {
theme = savedTheme;
document.documentElement.classList.add(theme);
}
if (savedCurrency) {
selectedCurrency = savedCurrency;
}
}
// Format Currency
function formatCurrency(amount, currency = 'usd') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency.toUpperCase(),
minimumFractionDigits: 2,
maximumFractionDigits: 6
}).format(amount);
}
// Copy to Clipboard
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
showToast('Copied to clipboard!', 'success');
}).catch(err => {
console.error('Copy failed:', err);
showToast('Copy failed', 'error');
});
}
// Show Toast Notification
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transition-all duration-300 transform translate-y-0 opacity-100 ${
type === 'success' ? 'bg-green-900 text-green-100' :
type === 'error' ? 'bg-red-900 text-red-100' :
'bg-dark-800 text-gray-100'
}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.transform = 'translateY(100%)';
toast.style.opacity = '0';
setTimeout(() => document.body.removeChild(toast), 300);
}, 3000);
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
loadPreferences();
initializeDashboard();
// Handle clicks outside search to close results
document.addEventListener('click', (event) => {
if (!event.target.closest('#searchContainer')) {
hideSearchResults();
}
});
});
// Export functions for use in templates
window.cryptoUtils = {
formatCurrency,
formatTimeAgo,
copyToClipboard,
showToast,
loadTopGainers,
loadNews
};
// Simplex noise for chart background (placeholder)
class CryptoChartSimulator {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
if (!this.canvas) return;
this.ctx = this.canvas.getContext('2d');
this.width = this.canvas.width;
this.height = this.canvas.height;
this.setupChart();
this.animate();
}
setupChart() {
// Chart setup logic would go here
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = '#0ea5e9';
}
animate() {
// Animation logic for demo chart
requestAnimationFrame(() => this.animate());
}
}