From 7a420ef97514504f92f758ed4ae92e090270a9d8 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Tue, 24 Feb 2026 10:25:06 -0500 Subject: [PATCH] refactor(theme): fix theme consistency and persistence - Apply server-side theme rendering to authenticated pages - bookshelf.templ: use dynamic theme-{ user.Theme } - admin pages: use dynamic theme rendering - Add progressive enhancement for public pages - index.templ, login.templ, register.templ: inline localStorage check - Prevents theme flash on page load - Consolidate wood theme logic into theme.ts - Move wood gradient handling from header.ts to theme.ts - Apply wood themes consistently via applyTheme() - Export applyTheme to window for use by header.ts - Fix theme selector by adding theme.js to header template --- templates/bookshelf.templ | 2 +- templates/index.templ | 9 +++++++ templates/login.templ | 9 +++++++ templates/register.templ | 9 +++++++ web/src/header.ts | 38 ++++-------------------------- web/src/theme.ts | 49 +++++++++++++++++++++++++++++++++------ 6 files changed, 75 insertions(+), 41 deletions(-) diff --git a/templates/bookshelf.templ b/templates/bookshelf.templ index e3a6123..c73c14a 100644 --- a/templates/bookshelf.templ +++ b/templates/bookshelf.templ @@ -12,7 +12,7 @@ templ BookShelf(user User, libraries []LibraryData) { - + @Header(user, "/bookshelf")
diff --git a/templates/index.templ b/templates/index.templ index ea1a49c..f0ef6e1 100644 --- a/templates/index.templ +++ b/templates/index.templ @@ -12,6 +12,15 @@ templ Index(loggedIn bool) { +
diff --git a/templates/login.templ b/templates/login.templ index 4d23edf..21ecdea 100644 --- a/templates/login.templ +++ b/templates/login.templ @@ -13,6 +13,15 @@ templ Login(sessionExpired bool, deleted bool) { +

Login to Bookhoard

diff --git a/templates/register.templ b/templates/register.templ index f502c0a..9c6e473 100644 --- a/templates/register.templ +++ b/templates/register.templ @@ -13,6 +13,15 @@ templ Register() { +

Register for Bookhoard

diff --git a/web/src/header.ts b/web/src/header.ts index bc3cc7c..fe4893d 100644 --- a/web/src/header.ts +++ b/web/src/header.ts @@ -27,39 +27,11 @@ const toggleUserMenu = (): void => { }; const changeThemeTo = (theme: string): void => { - // Apply the theme - if (theme.startsWith('wood-')) { - // Apply wood background theme - document.body.className = `theme-${theme}`; - - // Set wood background style - let woodGradient = ''; - switch (theme) { - case 'wood-light': - woodGradient = 'linear-gradient(135deg, #deb887 0%, #d2a679 50%, #c9975b 100%)'; - break; - case 'wood-dark': - woodGradient = 'linear-gradient(135deg, #8b7355 0%, #6b5344 50%, #5a4636 100%)'; - break; - case 'wood-mahogany': - woodGradient = 'linear-gradient(135deg, #a0522d 0%, #8b4513 50%, #7a3c10 100%)'; - break; - } - - document.body.style.background = woodGradient; - document.body.style.backgroundSize = 'cover'; - document.body.style.backgroundAttachment = 'fixed'; - } else { - // Apply regular theme - document.body.className = `theme-${theme}`; - document.body.style.background = ''; - document.body.style.backgroundSize = ''; - document.body.style.backgroundAttachment = ''; + // Apply the theme using the consolidated function from theme.ts + if ((window as any).applyTheme) { + (window as any).applyTheme(theme); } - - // Save to localStorage - localStorage.setItem('theme', theme); - + // Save to server if logged in const token = localStorage.getItem('token'); if (token) { @@ -72,7 +44,7 @@ const changeThemeTo = (theme: string): void => { body: JSON.stringify({ theme }) }).catch(err => console.log('Theme save failed', err)); } - + // Close dropdown const dropdown = document.getElementById('theme-dropdown'); if (dropdown) { diff --git a/web/src/theme.ts b/web/src/theme.ts index 275bf69..22e5fe1 100644 --- a/web/src/theme.ts +++ b/web/src/theme.ts @@ -1,6 +1,6 @@ // Theme management functionality -type ThemeType = +type ThemeType = | 'tokyo-night' | 'dracula' | 'nord' @@ -11,15 +11,47 @@ type ThemeType = | 'catppuccin-mocha' | 'catppuccin-macchiato' | 'catppuccin-frappe' - | 'catppuccin-latte'; + | 'catppuccin-latte' + | 'wood-light' + | 'wood-dark' + | 'wood-mahogany'; const DEFAULT_THEME: ThemeType = 'tokyo-night'; const THEME_STORAGE_KEY = 'theme'; const TOKEN_STORAGE_KEY = 'token'; // Apply theme to document body -const applyTheme = (theme: ThemeType): void => { - document.body.className = `theme-${theme}`; +const applyTheme = (theme: string): void => { + // Handle wood themes with gradients + if (theme.startsWith('wood-')) { + document.body.className = `theme-${theme}`; + + let woodGradient = ''; + switch (theme) { + case 'wood-light': + woodGradient = 'linear-gradient(135deg, #deb887 0%, #d2a679 50%, #c9975b 100%)'; + break; + case 'wood-dark': + woodGradient = 'linear-gradient(135deg, #8b7355 0%, #6b5344 50%, #5a4636 100%)'; + break; + case 'wood-mahogany': + woodGradient = 'linear-gradient(135deg, #a0522d 0%, #8b4513 50%, #7a3c10 100%)'; + break; + default: + woodGradient = ''; + } + + document.body.style.background = woodGradient; + document.body.style.backgroundSize = 'cover'; + document.body.style.backgroundAttachment = 'fixed'; + } else { + // Apply regular theme + document.body.className = `theme-${theme}`; + document.body.style.background = ''; + document.body.style.backgroundSize = ''; + document.body.style.backgroundAttachment = ''; + } + localStorage.setItem(THEME_STORAGE_KEY, theme); }; @@ -64,16 +96,16 @@ const changeTheme = async (): Promise => { const loadUserTheme = async (): Promise => { const token = localStorage.getItem(TOKEN_STORAGE_KEY); if (!token) return; - + try { const response = await fetch('/api/auth/profile', { headers: { 'Authorization': `Bearer ${token}` } }); - + if (response.ok) { const data = await response.json(); if (data.theme) { - applyTheme(data.theme); + applyTheme(data.theme as string); } } } catch { @@ -127,3 +159,6 @@ if (typeof document !== 'undefined') { // Make changeTheme available globally for HTML onchange attribute (window as any).changeTheme = changeTheme; + +// Export applyTheme to window for use by header.ts +(window as any).applyTheme = applyTheme;