// Profile page JavaScript functionality // Handles user profile data loading and interactions let currentUser = null; let listeningData = null; // Load profile data on page initialization function loadProfileData() { console.log('Loading profile data...'); // Load user info fetch('/api/asteroid/user/profile') .then(response => response.json()) .then(result => { // api-output wraps response in {status, message, data} const data = result.data || result; if (data.status === 'success') { currentUser = data.user; updateProfileDisplay(data.user); } else { console.error('Failed to load profile:', data.message); showError('Failed to load profile data'); } }) .catch(error => { console.error('Error loading profile:', error); showError('Error loading profile data'); }); // Load listening statistics loadListeningStats(); // Load recent tracks loadRecentTracks(); // Load top artists loadTopArtists(); } function updateProfileDisplay(user) { // Update basic user info updateElement('username', user.username || 'Unknown User'); updateElement('user-role', formatRole(user.role || 'listener')); updateElement('join-date', formatDate(user.created_at || new Date())); updateElement('last-active', formatRelativeTime(user.last_active || new Date())); // Show/hide admin link based on role const adminLink = document.querySelector('[data-show-if-admin]'); if (adminLink) { adminLink.style.display = (user.role === 'admin') ? 'inline' : 'none'; } } function loadListeningStats() { fetch('/api/asteroid/user/listening-stats') .then(response => response.json()) .then(result => { const data = result.data || result; if (data.status === 'success') { const stats = data.stats; updateElement('total-listen-time', formatDuration(stats.total_listen_time || 0)); updateElement('tracks-played', stats.tracks_played || 0); updateElement('session-count', stats.session_count || 0); updateElement('favorite-genre', stats.favorite_genre || 'Unknown'); } }) .catch(error => { console.error('Error loading listening stats:', error); // Set default values updateElement('total-listen-time', '0h 0m'); updateElement('tracks-played', '0'); updateElement('session-count', '0'); updateElement('favorite-genre', 'Unknown'); }); } function loadRecentTracks() { fetch('/api/asteroid/user/recent-tracks?limit=3') .then(response => response.json()) .then(result => { const data = result.data || result; if (data.status === 'success' && data.tracks && data.tracks.length > 0) { data.tracks.forEach((track, index) => { const trackNum = index + 1; updateElement(`recent-track-${trackNum}-title`, track.title || 'Unknown Track'); updateElement(`recent-track-${trackNum}-artist`, track.artist || 'Unknown Artist'); updateElement(`recent-track-${trackNum}-duration`, formatDuration(track.duration || 0)); updateElement(`recent-track-${trackNum}-played-at`, formatRelativeTime(track.played_at)); }); } else { // Hide empty track items for (let i = 1; i <= 3; i++) { const trackItem = document.querySelector(`[data-text="recent-track-${i}-title"]`)?.closest('.track-item'); if (trackItem && (!data.tracks || !data.tracks[i-1])) { trackItem.style.display = 'none'; } } } }) .catch(error => { console.error('Error loading recent tracks:', error); }); } function loadTopArtists() { fetch('/api/asteroid/user/top-artists?limit=5') .then(response => response.json()) .then(result => { const data = result.data || result; if (data.status === 'success' && data.artists && data.artists.length > 0) { data.artists.forEach((artist, index) => { const artistNum = index + 1; updateElement(`top-artist-${artistNum}`, artist.name || 'Unknown Artist'); updateElement(`top-artist-${artistNum}-plays`, `${artist.play_count || 0} plays`); }); } else { // Hide empty artist items for (let i = 1; i <= 5; i++) { const artistItem = document.querySelector(`[data-text="top-artist-${i}"]`)?.closest('.artist-item'); if (artistItem && (!data.artists || !data.artists[i-1])) { artistItem.style.display = 'none'; } } } }) .catch(error => { console.error('Error loading top artists:', error); }); } function loadMoreRecentTracks() { // TODO: Implement pagination for recent tracks console.log('Loading more recent tracks...'); showMessage('Loading more tracks...', 'info'); } function editProfile() { // TODO: Implement profile editing modal or redirect console.log('Edit profile clicked'); showMessage('Profile editing coming soon!', 'info'); } function exportListeningData() { console.log('Exporting listening data...'); showMessage('Preparing data export...', 'info'); fetch('/api/asteroid/user/export-data', { method: 'POST' }) .then(response => response.blob()) .then(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = `asteroid-listening-data-${currentUser?.username || 'user'}.json`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); showMessage('Data exported successfully!', 'success'); }) .catch(error => { console.error('Error exporting data:', error); showMessage('Failed to export data', 'error'); }); } function clearListeningHistory() { if (!confirm('Are you sure you want to clear your listening history? This action cannot be undone.')) { return; } console.log('Clearing listening history...'); showMessage('Clearing listening history...', 'info'); fetch('/api/asteroid/user/clear-history', { method: 'POST' }) .then(response => response.json()) .then(data => { if (data.status === 'success') { showMessage('Listening history cleared successfully!', 'success'); // Reload the page data setTimeout(() => { location.reload(); }, 1500); } else { showMessage('Failed to clear history: ' + data.message, 'error'); } }) .catch(error => { console.error('Error clearing history:', error); showMessage('Failed to clear history', 'error'); }); } // Utility functions function updateElement(dataText, value) { const element = document.querySelector(`[data-text="${dataText}"]`); if (element && value !== undefined && value !== null) { element.textContent = value; } } function formatRole(role) { const roleMap = { 'admin': '👑 Admin', 'dj': '🎧 DJ', 'listener': '🎵 Listener' }; return roleMap[role] || role; } function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); } function formatRelativeTime(dateString) { const date = new Date(dateString); const now = new Date(); const diffMs = now - date; const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffMinutes = Math.floor(diffMs / (1000 * 60)); if (diffDays > 0) { return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; } else if (diffHours > 0) { return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`; } else if (diffMinutes > 0) { return `${diffMinutes} minute${diffMinutes > 1 ? 's' : ''} ago`; } else { return 'Just now'; } } function formatDuration(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); if (hours > 0) { return `${hours}h ${minutes}m`; } else { return `${minutes}m`; } } function showMessage(message, type = 'info') { // Create a simple toast notification const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 4px; color: white; font-weight: bold; z-index: 1000; opacity: 0; transition: opacity 0.3s ease; `; // Set background color based on type const colors = { 'info': '#007bff', 'success': '#28a745', 'error': '#dc3545', 'warning': '#ffc107' }; toast.style.backgroundColor = colors[type] || colors.info; document.body.appendChild(toast); // Fade in setTimeout(() => { toast.style.opacity = '1'; }, 100); // Remove after 3 seconds setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => { document.body.removeChild(toast); }, 300); }, 3000); } function showError(message) { showMessage(message, 'error'); } // Password change handler function changePassword(event) { event.preventDefault(); const currentPassword = document.getElementById('current-password').value; const newPassword = document.getElementById('new-password').value; const confirmPassword = document.getElementById('confirm-password').value; const messageDiv = document.getElementById('password-message'); // Client-side validation if (newPassword.length < 8) { messageDiv.textContent = 'New password must be at least 8 characters'; messageDiv.className = 'message error'; return false; } if (newPassword !== confirmPassword) { messageDiv.textContent = 'New passwords do not match'; messageDiv.className = 'message error'; return false; } // Send request to API const formData = new FormData(); formData.append('current-password', currentPassword); formData.append('new-password', newPassword); fetch('/api/asteroid/user/change-password', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.status === 'success' || (data.data && data.data.status === 'success')) { messageDiv.textContent = 'Password changed successfully!'; messageDiv.className = 'message success'; document.getElementById('change-password-form').reset(); } else { messageDiv.textContent = data.message || data.data?.message || 'Failed to change password'; messageDiv.className = 'message error'; } }) .catch(error => { console.error('Error changing password:', error); messageDiv.textContent = 'Error changing password'; messageDiv.className = 'message error'; }); return false; }