diff --git a/frontend/js/app.js b/frontend/js/app.js index b713f96..d77082a 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -18,6 +18,7 @@ import * as teams from './views/teams.js'; import * as admin from './views/admin.js'; import * as designer from './views/designer.js'; import * as playlists from './views/playlists.js'; +import { applyBranding } from './branding.js'; const app = document.getElementById('app'); const sidebar = document.querySelector('.sidebar'); @@ -203,6 +204,7 @@ function updateSidebarUser() { // Initialize if (isAuthenticated()) { connectSocket(); + applyBranding(); } // Register PWA service worker diff --git a/frontend/js/branding.js b/frontend/js/branding.js new file mode 100644 index 0000000..3525132 --- /dev/null +++ b/frontend/js/branding.js @@ -0,0 +1,59 @@ +// Applies the current user's saved white-label config to the DOM. +// Runs once after login/route bootstrap. Without this, saved values in the +// white_labels table are read into the Settings form but never applied to +// the actual page — so users see "ScreenTinker" and default colors after +// every reload, as if their save reverted. + +let applied = false; + +export async function applyBranding() { + if (applied) return; + applied = true; + + const token = localStorage.getItem('token'); + if (!token) return; + + let wl; + try { + const res = await fetch('/api/white-label', { headers: { Authorization: `Bearer ${token}` } }); + if (!res.ok) return; + wl = await res.json(); + } catch { return; } + if (!wl) return; + + const root = document.documentElement; + if (wl.primary_color) root.style.setProperty('--accent', wl.primary_color); + if (wl.bg_color) { + root.style.setProperty('--bg-primary', wl.bg_color); + const meta = document.querySelector('meta[name="theme-color"]'); + if (meta) meta.setAttribute('content', wl.bg_color); + } + + if (wl.brand_name) { + document.title = wl.brand_name; + const span = document.querySelector('.sidebar-header .logo span'); + if (span) span.textContent = wl.brand_name; + } + + if (wl.favicon_url) { + document.querySelectorAll('link[rel="icon"], link[rel="apple-touch-icon"]').forEach(l => { + l.setAttribute('href', wl.favicon_url); + }); + } + + if (wl.custom_css) { + let style = document.getElementById('wl-custom-css'); + if (!style) { + style = document.createElement('style'); + style.id = 'wl-custom-css'; + document.head.appendChild(style); + } + style.textContent = wl.custom_css; + } +} + +// Force a re-apply (called from settings.js after save) +export function resetBranding() { + applied = false; + return applyBranding(); +} diff --git a/frontend/js/views/settings.js b/frontend/js/views/settings.js index 830e474..7e15bb0 100644 --- a/frontend/js/views/settings.js +++ b/frontend/js/views/settings.js @@ -2,6 +2,7 @@ import { api } from '../api.js'; import { showToast } from '../components/toast.js'; import { getLanguage, setLanguage, getAvailableLanguages } from '../i18n.js'; import { esc } from '../utils.js'; +import { resetBranding } from '../branding.js'; export async function render(container) { const serverUrl = `${window.location.protocol}//${window.location.host}`; @@ -368,6 +369,7 @@ async function loadWhiteLabel() { hide_branding: document.getElementById('wlHideBranding').checked ? 1 : 0, }) }); + await resetBranding(); showToast('Branding saved', 'success'); } catch (err) { showToast(err.message, 'error');