/** * InvestEdu - Main JavaScript File * Initializes the application and sets up global event listeners */ // Initialize application when DOM is ready document.addEventListener('DOMContentLoaded', function() { console.log('InvestEdu Application Initialized'); // Initialize common features initRouteLinks(); initPasswordToggles(); initFormValidations(); initTooltips(); initSidebar(); // Initialize page-specific features const page = document.body.getAttribute('data-page'); if (page === 'dashboard') { initDashboard(); } }); /** * Initialize password visibility toggles */ function initPasswordToggles() { if (typeof Utils === 'undefined') { return; } document.querySelectorAll('[data-toggle-password]').forEach(button => { button.addEventListener('click', function() { const targetId = this.getAttribute('data-toggle-password'); Utils.togglePasswordVisibility(targetId); }); }); } /** * Initialize form validations */ function initFormValidations() { if (typeof Forms === 'undefined') { return; } // Add real-time validation to forms document.querySelectorAll('form[data-validate]').forEach(form => { const fields = form.querySelectorAll('input, select, textarea'); fields.forEach(field => { field.addEventListener('blur', function() { // Validate on blur const rules = JSON.parse(form.getAttribute('data-rules') || '{}'); const fieldRules = rules[this.name] || {}; const validation = Forms.validateField(this, fieldRules); if (!validation.isValid) { Forms.showFieldError(this, validation.errors[0]); } else { Forms.clearFieldError(this); } }); }); }); } /** * Initialize route-based navigation links */ function initRouteLinks() { if (typeof RouteUtils === 'undefined') { return; } RouteUtils.wireRouteLinks(); } /** * Initialize tooltips */ function initTooltips() { // Simple tooltip implementation document.querySelectorAll('[data-tooltip]').forEach(element => { element.addEventListener('mouseenter', function() { const tooltip = document.createElement('div'); tooltip.className = 'tooltip absolute bg-slate-900 text-white text-xs px-2 py-1 rounded z-50'; tooltip.textContent = this.getAttribute('data-tooltip'); tooltip.style.top = `${this.offsetTop - 30}px`; tooltip.style.left = `${this.offsetLeft + this.offsetWidth / 2}px`; tooltip.style.transform = 'translateX(-50%)'; document.body.appendChild(tooltip); this._tooltip = tooltip; }); element.addEventListener('mouseleave', function() { if (this._tooltip) { this._tooltip.remove(); this._tooltip = null; } }); }); } /** * Initialize sidebar (mobile menu) */ function initSidebar() { const sidebarToggle = document.getElementById('sidebar-toggle'); const sidebar = document.getElementById('sidebar'); const sidebarOverlay = document.getElementById('sidebar-overlay'); if (sidebarToggle && sidebar) { sidebarToggle.addEventListener('click', function() { sidebar.classList.toggle('open'); if (sidebarOverlay) { sidebarOverlay.classList.toggle('hidden'); } }); } // Close sidebar when clicking overlay if (sidebarOverlay) { sidebarOverlay.addEventListener('click', function() { sidebar.classList.remove('open'); this.classList.add('hidden'); }); } } /** * Global error handler */ window.addEventListener('error', function(event) { console.error('Global Error:', event.error); // You can add error reporting here }); /** * Initialize dashboard page */ async function initDashboard() { try { // Load dashboard data const data = await API.user.getDashboard(); // Update user info in sidebar updateUserInfo(data.user); // Update balances updateBalances(data.user, data.statistics); // Update recent activity updateRecentActivity(data.recent_transactions, data.recent_activity); // Update community transfers updateCommunityTransfers(data); // Update statistics updateStatistics(data.statistics); } catch (error) { console.error('Failed to load dashboard:', error); // If unauthenticated, redirect to login (handled in API.js, but check here too) if (error.status === 401 || error.status === 419) { if (window.location.pathname !== '/login.html' && !window.location.pathname.includes('login')) { window.location.href = '/login.html'; return; } } // Show error message to user if (error.response?.message) { Utils?.showToast?.(error.response.message, 'error'); } else if (error.message && error.message.includes('unauthenticated')) { Utils?.showToast?.('Please log in to continue', 'error'); setTimeout(() => { window.location.href = '/login.html'; }, 2000); } } } /** * Get initials from name */ function getInitials(name) { if (!name) return 'U'; const parts = name.trim().split(' '); if (parts.length >= 2) { return (parts[0][0] + parts[1][0]).toUpperCase(); } return name.substring(0, 2).toUpperCase(); } /** * Update transfer section visibility based on profit growths */ async function updateTransferSection(statistics) { const transferSection = document.getElementById('transferSection'); if (!transferSection) return; try { // Load user's profit growths const response = await API.user.getProfitGrowths({ per_page: 100 }); const profitGrowths = response.data || response || []; // Filter for active/paused profit growths const activeProfitGrowths = profitGrowths.filter(pg => pg.status === 'active' || pg.status === 'paused' ); // Show transfer section only if user has at least one active profit growth if (activeProfitGrowths.length > 0) { transferSection.classList.remove('hidden'); } else { transferSection.classList.add('hidden'); } } catch (error) { console.error('Failed to load profit growths for transfer section:', error); // Hide section on error to avoid confusion transferSection.classList.add('hidden'); } } /** * Update user info in sidebar */ function updateUserInfo(user) { if (!user) return; // Update avatar const avatarContainer = document.getElementById('sidebarAvatarContainer'); const avatarImage = document.getElementById('sidebarAvatarImage'); const avatarInitials = document.getElementById('sidebarAvatarInitials'); if (avatarContainer && avatarImage && avatarInitials) { // Get initials for fallback const displayName = user.display_name || user.name || user.username || 'User'; const initials = getInitials(displayName); avatarInitials.textContent = initials; // If user has an avatar_url, use it if (user.avatar_url) { // Ensure the URL is absolute if it's a relative path let imageUrl = user.avatar_url; if (imageUrl.startsWith('/storage/')) { // If it's a relative path, use it as-is (browser will resolve it) imageUrl = imageUrl; } else if (!imageUrl.startsWith('http://') && !imageUrl.startsWith('https://')) { // If no protocol, assume it's relative to root imageUrl = '/' + imageUrl.replace(/^\//, ''); } avatarImage.src = imageUrl; avatarImage.onload = function() { // Image loaded successfully avatarImage.classList.remove('hidden'); avatarInitials.classList.add('hidden'); }; avatarImage.onerror = function() { // If image fails to load, fall back to initials console.error('Failed to load avatar image:', imageUrl); avatarImage.classList.add('hidden'); avatarInitials.classList.remove('hidden'); }; } else { // Fall back to initials avatarImage.classList.add('hidden'); avatarInitials.classList.remove('hidden'); } } // Update name const nameElement = document.getElementById('sidebarUserName'); if (nameElement) { nameElement.textContent = user.display_name || user.name || user.username || 'User'; } // Update member status (could be based on balance or other criteria) const memberElement = document.getElementById('sidebarUserStatus'); if (memberElement) { const balance = parseFloat(user.real_balance || 0); memberElement.textContent = balance > 0 ? 'Active Member' : 'Member'; } } /** * Update balance displays */ async function updateBalances(user, statistics) { if (!user) return; // Real balance - Primary display (USD) const realBalanceElement = document.getElementById('realWalletBalance'); if (realBalanceElement) { const balance = parseFloat(user.real_balance || 0); realBalanceElement.textContent = `$${balance.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; // Calculate and display BTC equivalent const btcElement = document.getElementById('realWalletBalanceBTC'); if (btcElement && balance > 0) { try { // Fetch current BTC price from CoinGecko API (free, no API key needed) const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd'); const data = await response.json(); const btcPrice = data.bitcoin?.usd || 42000; // Fallback to ~$42k if API fails // Calculate BTC equivalent const btcAmount = balance / btcPrice; btcElement.textContent = `≈ ${btcAmount.toFixed(8)} BTC`; } catch (error) { console.error('Failed to fetch BTC price:', error); // Fallback: use approximate price of $42,000 const btcAmount = balance / 42000; btcElement.textContent = `≈ ${btcAmount.toFixed(8)} BTC`; } } else if (btcElement) { btcElement.textContent = '≈ 0.00000000 BTC'; } } else { // Fallback: try to find by class selector (for backward compatibility) const fallbackElement = document.querySelector('.border-real h2'); if (fallbackElement) { const balance = parseFloat(user.real_balance || 0); fallbackElement.textContent = `$${balance.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } } // Profit Growth Balance (sum of all profit growths' current_balance) const profitGrowthBalanceElement = document.getElementById('totalProfitGrowthBalance'); if (profitGrowthBalanceElement) { // Use total_profit_growth_balance from statistics if available, otherwise calculate let totalBalance = 0; if (statistics && statistics.total_profit_growth_balance !== undefined) { totalBalance = parseFloat(statistics.total_profit_growth_balance || 0); } else { // Fallback: use simulated_balance (legacy) totalBalance = parseFloat(user.simulated_balance || 0); } profitGrowthBalanceElement.textContent = `$${totalBalance.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } // Legacy: Simulated balance (for backward compatibility - try to find by class if ID not found) const simBalanceElement = document.querySelector('.border-sim h2:not(#totalProfitGrowthBalance)'); if (simBalanceElement && !profitGrowthBalanceElement) { const balance = parseFloat(user.simulated_balance || 0); simBalanceElement.textContent = `$${balance.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } } /** * Update recent activity table */ function updateRecentActivity(transactions, activityLogs) { const tbody = document.querySelector('table tbody'); if (!tbody) return; // Clear existing rows (except the first 3 which are examples) const existingRows = tbody.querySelectorAll('tr'); existingRows.forEach(row => row.remove()); // Combine and sort by date (most recent first) const allActivities = []; // Add transactions (exclude pending deposits - they shouldn't show until admin confirms) if (transactions && transactions.length > 0) { transactions .filter(tx => { // Don't show pending deposits in recent activity // They will only appear after admin confirms them if (tx.type === 'deposit' && tx.status === 'pending') { return false; } return true; }) .slice(0, 5) .forEach(tx => { allActivities.push({ type: 'transaction', action: tx.type, fund_type: tx.fund_type, amount: tx.amount, currency: tx.currency || 'USD', description: tx.description || `${tx.type} transaction`, status: tx.status, date: tx.created_at }); }); } // Add activity logs if (activityLogs && activityLogs.length > 0) { activityLogs.slice(0, 5).forEach(log => { allActivities.push({ type: 'activity', action: log.action, fund_type: log.fund_type, amount: log.amount, currency: log.currency || 'USD', description: log.description || log.action, status: 'completed', date: log.created_at }); }); } // Sort by date (most recent first) allActivities.sort((a, b) => new Date(b.date) - new Date(a.date)); // Display top 5 allActivities.slice(0, 5).forEach(activity => { const row = createActivityRow(activity); tbody.appendChild(row); }); // If no activities, show empty state if (allActivities.length === 0) { const row = document.createElement('tr'); row.innerHTML = `
No recent activity
${formatActionName(activity.action)}
${activity.description || ''}
Received
$${parseFloat(transfer.amount || 0).toFixed(2)} from ${fromUser.username || fromUser.name || 'User'}
Sent
$${parseFloat(transfer.amount || 0).toFixed(2)} to ${toUser.username || toUser.name || 'User'}
No transfers yet