import { syncManager } from './accounts/pocketbase.js';
import { authManager } from './accounts/auth.js';
import { navigate } from './router.js';
import { MusicAPI } from './music-api.js';
import { apiSettings } from './storage.js';
import { debounce, escapeHtml } from './utils.js';
// objects execution february 29th 2027
const profilePage = document.getElementById('page-profile');
const editProfileModal = document.getElementById('edit-profile-modal');
const editProfileBtn = document.getElementById('profile-edit-btn');
const viewMyProfileBtn = document.getElementById('view-my-profile-btn');
const editUsername = document.getElementById('edit-profile-username');
const editDisplayName = document.getElementById('edit-profile-display-name');
const editAvatar = document.getElementById('edit-profile-avatar');
const editBanner = document.getElementById('edit-profile-banner');
const editStatusSearch = document.getElementById('edit-profile-status-search');
const editStatusJson = document.getElementById('edit-profile-status-json');
const statusSearchResults = document.getElementById('status-search-results');
const statusPreview = document.getElementById('status-preview');
const clearStatusBtn = document.getElementById('clear-status-btn');
const editFavoriteAlbumsList = document.getElementById('edit-favorite-albums-list');
const editFavoriteAlbumsSearch = document.getElementById('edit-favorite-albums-search');
const editFavoriteAlbumsResults = document.getElementById('edit-favorite-albums-results');
const editAbout = document.getElementById('edit-profile-about');
const editWebsite = document.getElementById('edit-profile-website');
const editLastfm = document.getElementById('edit-profile-lastfm');
const privacyPlaylists = document.getElementById('privacy-playlists-toggle');
const privacyLastfm = document.getElementById('privacy-lastfm-toggle');
const saveProfileBtn = document.getElementById('edit-profile-save');
const cancelProfileBtn = document.getElementById('edit-profile-cancel');
const usernameError = document.getElementById('username-error');
let currentProfileUsername = null;
let currentFavoriteAlbums = [];
const api = new MusicAPI(apiSettings);
async function uploadImage(file) {
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/upload', { method: 'POST', body: formData });
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
const data = await response.json();
return data.url;
} catch (error) {
console.error('Upload error:', error);
throw error;
}
}
function setupImageUploadControl(idPrefix) {
const urlInput = document.getElementById(idPrefix);
const fileInput = document.getElementById(idPrefix + '-file');
const uploadBtn = document.getElementById(idPrefix + '-upload-btn');
const toggleBtn = document.getElementById(idPrefix + '-toggle-btn');
const statusEl = document.getElementById(idPrefix + '-upload-status');
if (!urlInput || !fileInput || !uploadBtn || !toggleBtn || !statusEl) return () => {};
let useUrl = false;
function updateUI() {
if (useUrl) {
uploadBtn.style.display = 'none';
urlInput.style.display = 'block';
toggleBtn.textContent = 'Upload';
} else {
uploadBtn.style.display = 'flex';
urlInput.style.display = 'none';
toggleBtn.textContent = 'or URL';
}
}
toggleBtn.addEventListener('click', () => {
useUrl = !useUrl;
updateUI();
});
uploadBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
if (!file.type.startsWith('image/')) {
alert('Please select an image file');
return;
}
statusEl.style.display = 'block';
statusEl.textContent = 'Uploading...';
statusEl.style.color = 'var(--muted-foreground)';
uploadBtn.disabled = true;
try {
const url = await uploadImage(file);
urlInput.value = url;
statusEl.textContent = 'Done!';
statusEl.style.color = '#10b981';
setTimeout(() => {
statusEl.style.display = 'none';
}, 2000);
} catch (error) {
statusEl.textContent = 'Failed - try URL';
statusEl.style.color = '#ef4444';
} finally {
uploadBtn.disabled = false;
fileInput.value = '';
}
});
return (currentUrl) => {
urlInput.value = currentUrl || '';
useUrl = !!currentUrl;
updateUI();
statusEl.style.display = 'none';
};
}
const resetAvatarControl = setupImageUploadControl('edit-profile-avatar');
const resetBannerControl = setupImageUploadControl('edit-profile-banner');
export async function loadProfile(username) {
document.querySelectorAll('.page').forEach((p) => p.classList.remove('active'));
profilePage.classList.add('active');
document.getElementById('profile-banner').style.backgroundImage = '';
document.getElementById('profile-avatar').src = '/assets/appicon.png';
document.getElementById('profile-display-name').textContent = 'Loading...';
document.getElementById('profile-username').textContent = '@' + username;
document.getElementById('profile-status').style.display = 'none';
document.getElementById('profile-about').textContent = '';
document.getElementById('profile-website').style.display = 'none';
document.getElementById('profile-lastfm').style.display = 'none';
document.getElementById('profile-playlists-container').innerHTML = '';
const favAlbumsSection = document.getElementById('profile-favorite-albums-section');
const favAlbumsContainer = document.getElementById('profile-favorite-albums-container');
if (favAlbumsSection) favAlbumsSection.style.display = 'none';
if (favAlbumsContainer) favAlbumsContainer.innerHTML = '';
const recentSection = document.getElementById('profile-recent-scrobbles-section');
const recentContainer = document.getElementById('profile-recent-scrobbles-container');
if (recentSection) recentSection.style.display = 'none';
if (recentContainer) recentContainer.innerHTML = '';
const topArtistsSection = document.getElementById('profile-top-artists-section');
const topArtistsContainer = document.getElementById('profile-top-artists-container');
const topAlbumsSection = document.getElementById('profile-top-albums-section');
const topAlbumsContainer = document.getElementById('profile-top-albums-container');
const topTracksSection = document.getElementById('profile-top-tracks-section');
const topTracksContainer = document.getElementById('profile-top-tracks-container');
if (topArtistsSection) topArtistsSection.style.display = 'none';
if (topArtistsContainer) topArtistsContainer.innerHTML = '';
if (topAlbumsSection) topAlbumsSection.style.display = 'none';
if (topAlbumsContainer) topAlbumsContainer.innerHTML = '';
if (topTracksSection) topTracksSection.style.display = 'none';
if (topTracksContainer) topTracksContainer.innerHTML = '';
editProfileBtn.style.display = 'none';
const profile = await syncManager.getProfile(username);
if (!profile) {
document.getElementById('profile-display-name').textContent = 'User not found';
return;
}
currentProfileUsername = username;
document.getElementById('profile-display-name').textContent = profile.display_name || username;
if (profile.banner) document.getElementById('profile-banner').style.backgroundImage = `url('${profile.banner}')`;
if (profile.avatar_url) document.getElementById('profile-avatar').src = profile.avatar_url;
if (profile.status) {
const statusEl = document.getElementById('profile-status');
try {
const statusObj = JSON.parse(profile.status);
statusEl.innerHTML = `
Listening to:
${statusObj.text}
`;
statusEl.querySelector('.status-link').onclick = (e) => {
e.preventDefault();
navigate(statusObj.link);
};
} catch {
statusEl.textContent = `Listening to: ${profile.status}`;
}
statusEl.style.display = 'inline-flex';
}
if (profile.about) {
document.getElementById('profile-about').textContent = profile.about;
}
if (profile.website) {
const webEl = document.getElementById('profile-website');
webEl.href = profile.website;
webEl.style.display = 'inline-block';
}
if (profile.favorite_albums && profile.favorite_albums.length > 0) {
if (favAlbumsSection && favAlbumsContainer) {
favAlbumsSection.style.display = 'block';
favAlbumsContainer.innerHTML = profile.favorite_albums
.map((album) => {
const image = api.getCoverUrl(album.cover);
return `
${escapeHtml(album.description || '')}
No public playlists.
'; } } } export function openEditProfile() { syncManager.getUserData().then((data) => { if (!data || !data.profile) return; const p = data.profile; editUsername.value = p.username || ''; editDisplayName.value = p.display_name || ''; resetAvatarControl(p.avatar_url); resetBannerControl(p.banner); editStatusJson.value = p.status || ''; editStatusSearch.value = ''; if (p.status) { try { const statusObj = JSON.parse(p.status); showStatusPreview(statusObj); } catch { if (p.status.trim()) { editStatusSearch.value = p.status; hideStatusPreview(); } } } else { hideStatusPreview(); } currentFavoriteAlbums = p.favorite_albums || []; renderEditFavoriteAlbums(); editFavoriteAlbumsSearch.value = ''; editFavoriteAlbumsResults.style.display = 'none'; editAbout.value = p.about || ''; editWebsite.value = p.website || ''; editLastfm.value = p.lastfm_username || ''; privacyPlaylists.checked = p.privacy?.playlists !== 'private'; privacyLastfm.checked = p.privacy?.lastfm !== 'private'; editProfileModal.classList.add('active'); }); } async function saveProfile() { const newUsername = editUsername.value.trim(); if (!newUsername) { usernameError.textContent = 'Username cannot be empty'; usernameError.style.display = 'block'; return; } const currentUser = await syncManager.getUserData(); if (currentUser.profile.username !== newUsername) { const taken = await syncManager.isUsernameTaken(newUsername); if (taken) { usernameError.textContent = 'Username is already taken'; usernameError.style.display = 'block'; return; } } usernameError.style.display = 'none'; saveProfileBtn.disabled = true; saveProfileBtn.textContent = 'Saving...'; const data = { username: newUsername, display_name: editDisplayName.value.trim(), avatar_url: editAvatar.value.trim(), banner: editBanner.value.trim(), status: editStatusJson.value.trim() || (editStatusSearch.value.trim() ? editStatusSearch.value.trim() : ''), about: editAbout.value.trim(), website: editWebsite.value.trim(), favorite_albums: currentFavoriteAlbums, lastfm_username: editLastfm.value.trim(), privacy: { playlists: privacyPlaylists.checked ? 'public' : 'private', lastfm: privacyLastfm.checked ? 'public' : 'private', }, }; try { await syncManager.updateProfile(data); editProfileModal.classList.remove('active'); loadProfile(newUsername); if (window.location.pathname.includes('/user/@')) { window.history.replaceState(null, '', `/user/@${newUsername}`); } } catch (e) { alert('Failed to save profile. See console.'); console.error(e); } finally { saveProfileBtn.disabled = false; saveProfileBtn.textContent = 'Save Profile'; } } editProfileBtn.addEventListener('click', openEditProfile); cancelProfileBtn.addEventListener('click', () => editProfileModal.classList.remove('active')); saveProfileBtn.addEventListener('click', saveProfile); viewMyProfileBtn.addEventListener('click', async () => { const data = await syncManager.getUserData(); if (data && data.profile && data.profile.username) { navigate(`/user/@${data.profile.username}`); } else { openEditProfile(); } }); authManager.onAuthStateChanged((user) => { viewMyProfileBtn.style.display = user ? 'inline-block' : 'none'; }); function showStatusPreview(data) { document.getElementById('status-preview-img').src = data.image; document.getElementById('status-preview-title').textContent = data.title; document.getElementById('status-preview-subtitle').textContent = data.subtitle; statusPreview.style.display = 'flex'; editStatusSearch.style.display = 'none'; } function hideStatusPreview() { statusPreview.style.display = 'none'; editStatusSearch.style.display = 'block'; editStatusJson.value = ''; } clearStatusBtn.addEventListener('click', () => { hideStatusPreview(); editStatusSearch.value = ''; editStatusSearch.focus(); }); const performStatusSearch = debounce(async (query) => { if (!query) { statusSearchResults.style.display = 'none'; return; } try { const [tracks, albums] = await Promise.all([ api.searchTracks(query, { limit: 3 }), api.searchAlbums(query, { limit: 3 }), ]); statusSearchResults.innerHTML = ''; const createItem = (item, type) => { const div = document.createElement('div'); div.className = 'search-result-item'; const title = item.title; const subtitle = type === 'track' ? item.artist?.name || 'Unknown Artist' : item.artist?.name || 'Unknown Artist'; const image = api.getCoverUrl(item.album?.cover || item.cover); div.innerHTML = `