1// features/users/profile.js â extracted from app.js (lines 2361-2403). 2// Classic script: shares the global scope with every other core/ and features/ 3// script. Do NOT wrap in an IIFE and do NOT convert to an ES module â top-level 4// function declarations must attach to window for inline on* handlers, and 5// top-level const/let are shared across files. 6 7// "Maria Urdaneta" -> "MU", "Ysabel Revilla" -> "YR"; a single name gives 8// its first two letters; with no name at all, the email's first letter. 9function userInitials(name, email) { 10 const words = String(name || '').trim().split(/\s+/).filter(Boolean); 11 if (words.length >= 2) return (words[0][0] + words[words.length - 1][0]).toUpperCase(); 12 if (words.length === 1) return words[0].slice(0, 2).toUpperCase(); 13 return (String(email || 'U').trim()[0] || 'U').toUpperCase(); 14} 15 16// The top-right avatar button: the signed-in user's photo when they have 17// one, otherwise their initials (it was a hardcoded "CZ" for everyone). 18function renderTopbarAvatar(url, name, email) { 19 if (typeof profileMenuBtn === 'undefined' || !profileMenuBtn) return; 20 const initials = userInitials(name, email); 21 profileMenuBtn.innerHTML = url 22 ? `<img src="${encodeURI(url)}" alt="${escapeAttr(initials)}" />` 23 : `<span id="profileMenuInitials">${escapeHtml(initials)}</span>`; 24 profileMenuBtn.title = name ? `${name} â profile` : 'Profile'; 25} 26 27// Show initials from the session at once, then swap in the profile photo 28// once /api/me/profile answers (never blocks showing the app). 29async function refreshTopbarAvatar() { 30 const user = state.user || {}; 31 renderTopbarAvatar('', user.name, user.email); 32 try { 33 const profile = await api('/api/me/profile'); 34 const url = String(profile.profilePictureUrl || profile.profile_picture_url || '').trim(); 35 renderTopbarAvatar(url, profile.name || user.name, profile.email || user.email); 36 } catch (_error) { 37 // Initials stay; the profile endpoint failing is not worth a toast. 38 } 39} 40 41function renderProfileAvatarDisplay(url, name, email) { 42 const initials = userInitials(name, email); 43 profileAvatar.textContent = initials; 44 profileAvatar.style.backgroundImage = ''; 45 profileAvatar.style.backgroundSize = ''; 46 profileAvatar.style.backgroundPosition = ''; 47 if (url) { 48 profileAvatar.textContent = ''; 49 profileAvatar.style.backgroundImage = `url(${encodeURI(url)})`; 50 profileAvatar.style.backgroundSize = 'cover'; 51 profileAvatar.style.backgroundPosition = 'center'; 52 } 53 renderTopbarAvatar(url, name, email); 54} 55 56function setProfileModalData(data = {}) { 57 const name = String(data.name || state.user?.name || '').trim(); 58 const email = String(data.email || state.user?.email || '').trim(); 59 const userType = String(data.userType || data.user_type || state.userType || 'member'); 60 const jobTitle = String(data.jobTitle || data.job_title || '').trim(); 61 const profilePictureUrl = String(data.profilePictureUrl || data.profile_picture_url || '').trim(); 62 profileModalName.textContent = name || 'User'; 63 profileModalJobTitle.textContent = jobTitle || 'Add a job title'; 64 profileModalType.textContent = userType; 65 renderProfileAvatarDisplay(profilePictureUrl, name, email); 66 67 profileNameInput.value = name; 68 profileJobTitleInput.value = data.jobTitle || data.job_title || ''; 69 profileEmailInput.value = email; 70 profilePhoneInput.value = data.phone || ''; 71 profileMobileInput.value = data.mobilePhone || data.mobile_phone || ''; 72 profileLocationInput.value = data.location || ''; 73 profileBirthdayInput.value = formatDateInputValue(data.birthday || ''); 74 profileAnniversaryInput.value = formatDateInputValue(data.workAnniversary || data.work_anniversary || ''); 75} 76 77async function openProfileModal() { 78 profileFormError.textContent = ''; 79 profileMenu.classList.add('hidden'); 80 const profile = await api('/api/me/profile'); 81 setProfileModalData(profile); 82 profileModal.classList.remove('hidden'); 83}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.