// Cosmetic + role tweaks for the portal shell. Persisted in localStorage so // reloads stay coherent during prototyping. Applied to as data-* // attributes; tokens.css picks them up via selector overrides. // // `role` is the most important tweak — it switches which sidebar nav + which // pages are visible (end user / customer admin / partner admin). For real use // the role would come from Authentik group claims; the tweak lets us preview // all three views without standing up three orgs. export type ThemeMode = 'dark' | 'light' export type Density = 'comfy' | 'compact' export type Accent = 'signal' | 'cobalt' | 'coral' | 'moss' export type PortalRole = 'end-user' | 'customer-admin' | 'partner-admin' interface TweakState { theme: ThemeMode density: Density accent: Accent role: PortalRole } const STORAGE_KEY = 'dezky-portal-tweaks' const DEFAULTS: TweakState = { theme: 'light', density: 'comfy', accent: 'signal', role: 'customer-admin', } const state = ref({ ...DEFAULTS }) const hydrated = ref(false) function apply() { if (!import.meta.client) return const root = document.documentElement root.setAttribute('data-theme', state.value.theme) root.setAttribute('data-density', state.value.density) root.setAttribute('data-accent', state.value.accent) root.setAttribute('data-role', state.value.role) } function persist() { if (!import.meta.client) return try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state.value)) } catch { // localStorage can throw in private mode; tweaks are cosmetic so swallow. } } function hydrate() { if (!import.meta.client || hydrated.value) return try { const raw = localStorage.getItem(STORAGE_KEY) if (raw) { const parsed = JSON.parse(raw) as Partial state.value = { theme: parsed.theme ?? DEFAULTS.theme, density: parsed.density ?? DEFAULTS.density, accent: parsed.accent ?? DEFAULTS.accent, role: parsed.role ?? DEFAULTS.role, } } } catch { // ignore corrupt JSON } apply() hydrated.value = true } export const usePortalTweaks = () => { if (import.meta.client) hydrate() function set(key: K, value: TweakState[K]) { state.value = { ...state.value, [key]: value } apply() persist() } return { state, setTheme: (v: ThemeMode) => set('theme', v), setDensity: (v: Density) => set('density', v), setAccent: (v: Accent) => set('accent', v), setRole: (v: PortalRole) => set('role', v), } }