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 <noreply@anthropic.com>
This commit is contained in:
ScreenTinker 2026-04-22 19:38:46 -05:00
parent 281a735e84
commit 2959eaa149
3 changed files with 29 additions and 2 deletions

View file

@ -122,6 +122,7 @@ export const api = {
groupAssignPlaylist: (groupId, playlist_id) => request(`/groups/${groupId}/assign-playlist`, { method: 'POST', body: JSON.stringify({ playlist_id }) }), groupAssignPlaylist: (groupId, playlist_id) => request(`/groups/${groupId}/assign-playlist`, { method: 'POST', body: JSON.stringify({ playlist_id }) }),
// Current user // Current user
getMe: () => request('/auth/me'),
updateMe: (data) => request('/auth/me', { method: 'PUT', body: JSON.stringify(data) }), updateMe: (data) => request('/auth/me', { method: 'PUT', body: JSON.stringify(data) }),
// Admin - Users // Admin - Users

View file

@ -34,6 +34,22 @@ function getCurrentUser() {
} catch { return null; } } 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() { function route() {
// Cleanup previous view // Cleanup previous view
if (currentView && currentView.cleanup) currentView.cleanup(); if (currentView && currentView.cleanup) currentView.cleanup();
@ -205,8 +221,13 @@ function updateSidebarUser() {
if (isAuthenticated()) { if (isAuthenticated()) {
connectSocket(); connectSocket();
applyBranding(); 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 // Register PWA service worker
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw-admin.js').catch(() => {}); navigator.serviceWorker.register('/sw-admin.js').catch(() => {});

View file

@ -6,7 +6,11 @@ import { resetBranding } from '../branding.js';
export async function render(container) { export async function render(container) {
const serverUrl = `${window.location.protocol}//${window.location.host}`; 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 isSuperAdmin = user.role === 'superadmin';
const isAdmin = user.role === 'admin' || isSuperAdmin; const isAdmin = user.role === 'admin' || isSuperAdmin;
@ -325,7 +329,8 @@ async function loadWhiteLabel() {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
const headers = { Authorization: `Bearer ${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 user = JSON.parse(localStorage.getItem('user') || '{}');
const section = document.getElementById('whiteLabelSection'); const section = document.getElementById('whiteLabelSection');
if (section && user.plan_id !== 'enterprise' && user.role !== 'superadmin') { if (section && user.plan_id !== 'enterprise' && user.role !== 'superadmin') {