From 2959eaa149505dd4534ff6a885232d6bcadf2693 Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Wed, 22 Apr 2026 19:38:46 -0500 Subject: [PATCH] Refresh cached user so admin plan/role changes propagate The JWT only carries { id, email, role } and the server reads plan_id fresh from the DB per request, but the frontend cached the user object in localStorage at login and never refreshed it. After an admin changed a user's plan, the dashboard kept rendering the old plan until the user logged out and back in. Added api.getMe() and a refreshCurrentUser() helper that runs at startup and on every hashchange. Settings page now fetches the user fresh via api.getMe() on render, with localStorage as fallback. Co-Authored-By: Claude Opus 4.7 --- frontend/js/api.js | 1 + frontend/js/app.js | 21 +++++++++++++++++++++ frontend/js/views/settings.js | 9 +++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/frontend/js/api.js b/frontend/js/api.js index 748648c..6b7b91e 100644 --- a/frontend/js/api.js +++ b/frontend/js/api.js @@ -122,6 +122,7 @@ export const api = { groupAssignPlaylist: (groupId, playlist_id) => request(`/groups/${groupId}/assign-playlist`, { method: 'POST', body: JSON.stringify({ playlist_id }) }), // Current user + getMe: () => request('/auth/me'), updateMe: (data) => request('/auth/me', { method: 'PUT', body: JSON.stringify(data) }), // Admin - Users diff --git a/frontend/js/app.js b/frontend/js/app.js index d77082a..d6def43 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -34,6 +34,22 @@ function getCurrentUser() { } catch { return null; } } +// Refresh the cached user from the server. The server reads plan_id fresh +// from the DB on every request, but the frontend only wrote `user` into +// localStorage at login — so plan/role changes made by an admin weren't +// visible until the user logged out and back in. +async function refreshCurrentUser() { + const token = localStorage.getItem('token'); + if (!token) return; + try { + const res = await fetch('/api/auth/me', { headers: { Authorization: `Bearer ${token}` } }); + if (!res.ok) return; + const fresh = await res.json(); + localStorage.setItem('user', JSON.stringify(fresh)); + window.dispatchEvent(new CustomEvent('user-refreshed', { detail: fresh })); + } catch {} +} + function route() { // Cleanup previous view if (currentView && currentView.cleanup) currentView.cleanup(); @@ -205,8 +221,13 @@ function updateSidebarUser() { if (isAuthenticated()) { connectSocket(); applyBranding(); + refreshCurrentUser().then(() => updateSidebarUser()); } +// Refresh the cached user on every route transition so plan/role changes +// made by an admin propagate without requiring a re-login. +window.addEventListener('hashchange', () => { if (isAuthenticated()) refreshCurrentUser(); }); + // Register PWA service worker if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw-admin.js').catch(() => {}); diff --git a/frontend/js/views/settings.js b/frontend/js/views/settings.js index 7e15bb0..5c7c335 100644 --- a/frontend/js/views/settings.js +++ b/frontend/js/views/settings.js @@ -6,7 +6,11 @@ import { resetBranding } from '../branding.js'; export async function render(container) { const serverUrl = `${window.location.protocol}//${window.location.host}`; - const user = JSON.parse(localStorage.getItem('user') || '{}'); + // Fetch fresh user from the server — plan_id and role may have been changed + // by an admin since login. Fall back to localStorage if the request fails. + let user; + try { user = await api.getMe(); localStorage.setItem('user', JSON.stringify(user)); } + catch { user = JSON.parse(localStorage.getItem('user') || '{}'); } const isSuperAdmin = user.role === 'superadmin'; const isAdmin = user.role === 'admin' || isSuperAdmin; @@ -325,7 +329,8 @@ async function loadWhiteLabel() { const token = localStorage.getItem('token'); const headers = { Authorization: `Bearer ${token}` }; - // Only show white-label for enterprise/superadmin + // Only show white-label for enterprise/superadmin. + // Use the fresh user cached by render() above, which called api.getMe(). const user = JSON.parse(localStorage.getItem('user') || '{}'); const section = document.getElementById('whiteLabelSection'); if (section && user.plan_id !== 'enterprise' && user.role !== 'superadmin') {