Fix Avatar-Anzeige: Auth-Header bei Avatar-Requests mitsenden

Avatar-Bilder wurden per <img src="..."> geladen, was keinen Authorization-Header
mitsendet. Der Endpoint erfordert aber Auth, daher kam immer 401 zurück.
Jetzt werden alle Avatar-Requests per fetch() mit Bearer-Token geladen und als Blob-URL gesetzt.
This commit is contained in:
2026-03-26 19:23:58 +01:00
parent 3f3609c944
commit a1001bad68
2 changed files with 56 additions and 26 deletions

View File

@@ -162,17 +162,27 @@ function bindLoginForm() {
// ── Avatar Helper ────────────────────────────────────────
function loadAvatarImage(avatarEl, username) {
const img = new Image();
img.onload = () => {
avatarEl.innerHTML = '';
img.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;inset:0';
avatarEl.appendChild(img);
};
img.onerror = () => {
avatarEl.innerHTML = '';
avatarEl.textContent = (username || '?')[0].toUpperCase();
};
img.src = `/api/profile/avatar?t=${Date.now()}`;
const token = localStorage.getItem('token');
fetch(`/api/profile/avatar?t=${Date.now()}`, {
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
})
.then(res => {
if (!res.ok) throw new Error('No avatar');
return res.blob();
})
.then(blob => {
const img = new Image();
img.onload = () => {
avatarEl.innerHTML = '';
img.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;inset:0';
avatarEl.appendChild(img);
};
img.src = URL.createObjectURL(blob);
})
.catch(() => {
avatarEl.innerHTML = '';
avatarEl.textContent = (username || '?')[0].toUpperCase();
});
}
// ── Start ─────────────────────────────────────────────────

View File

@@ -4,6 +4,17 @@ import { renderMonth } from './views/month.js';
import { renderWeek } from './views/week.js';
import { renderAgenda } from './views/agenda.js';
// Fetch avatar image as blob URL (with auth header)
function fetchAvatarBlob() {
const token = localStorage.getItem('token');
return fetch(`/api/profile/avatar?t=${Date.now()}`, {
headers: token ? { 'Authorization': `Bearer ${token}` } : {}
}).then(res => {
if (!res.ok) throw new Error('No avatar');
return res.blob();
}).then(blob => URL.createObjectURL(blob));
}
// week start day global (loaded from settings)
let weekStartDay = 'monday';
@@ -751,9 +762,15 @@ export function openProfileModal() {
const img = document.getElementById('profile-avatar-img');
const removeBtn = document.getElementById('profile-avatar-remove');
if (profile.has_avatar) {
img.src = `/api/profile/avatar?t=${Date.now()}`;
img.classList.remove('hidden');
letter.classList.add('hidden');
fetchAvatarBlob().then(blobUrl => {
img.src = blobUrl;
img.classList.remove('hidden');
letter.classList.add('hidden');
}).catch(() => {
img.classList.add('hidden');
letter.classList.remove('hidden');
letter.textContent = (user.username || '?')[0].toUpperCase();
});
removeBtn.classList.remove('hidden');
} else {
img.classList.add('hidden');
@@ -899,18 +916,19 @@ function bindProfileModal() {
function updateTopbarAvatar(hasAvatar) {
const avatar = document.getElementById('user-avatar');
if (hasAvatar) {
const img = new Image();
img.onload = () => {
avatar.innerHTML = '';
img.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;inset:0';
avatar.appendChild(img);
};
img.onerror = () => {
fetchAvatarBlob().then(blobUrl => {
const img = new Image();
img.onload = () => {
avatar.innerHTML = '';
img.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;inset:0';
avatar.appendChild(img);
};
img.src = blobUrl;
}).catch(() => {
avatar.innerHTML = '';
const u = JSON.parse(localStorage.getItem('user')||'{}');
avatar.textContent = (u.username||'?')[0].toUpperCase();
};
img.src = `/api/profile/avatar?t=${Date.now()}`;
});
// Update localStorage so avatar persists across reloads
const u = JSON.parse(localStorage.getItem('user')||'{}');
u.has_avatar = true;
@@ -1047,9 +1065,11 @@ document.getElementById('crop-save').onclick = async () => {
showToast('Profilbild hochgeladen');
// Update profile modal avatar
const img = document.getElementById('profile-avatar-img');
img.src = `/api/profile/avatar?t=${Date.now()}`;
img.classList.remove('hidden');
document.getElementById('profile-avatar-letter').classList.add('hidden');
fetchAvatarBlob().then(blobUrl => {
img.src = blobUrl;
img.classList.remove('hidden');
document.getElementById('profile-avatar-letter').classList.add('hidden');
});
document.getElementById('profile-avatar-remove').classList.remove('hidden');
// Update topbar avatar
updateTopbarAvatar(true);