PageSourceSearch

https://famille-bretet.net/features/users/profile.js?v=1bce162

js famille-bretet.net collected 2026-09-25 08:20:57 UTC 3,944 bytes, 83 lines download raw bytes

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.