Add theme system with color presets, fix login page styling, persist auth session
Theme system with 5 color presets (Slate, Emerald, Violet, Amber, Rose) and light/dark/system mode. User menu in sidebar with theme picker and sign out. Login page uses standalone dark branded styling with autofill override. Auth persists in sessionStorage across refreshes.
This commit is contained in:
@@ -21,17 +21,39 @@ function decodeJwtPayload(token: string): { id: string; companyId: string; role:
|
||||
return JSON.parse(atob(payload))
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
token: null,
|
||||
user: null,
|
||||
companyId: null,
|
||||
function loadSession(): { token: string; user: User; companyId: string } | null {
|
||||
try {
|
||||
const raw = sessionStorage.getItem('forte-auth')
|
||||
if (!raw) return null
|
||||
return JSON.parse(raw)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function saveSession(token: string, user: User, companyId: string) {
|
||||
sessionStorage.setItem('forte-auth', JSON.stringify({ token, user, companyId }))
|
||||
}
|
||||
|
||||
function clearSession() {
|
||||
sessionStorage.removeItem('forte-auth')
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => {
|
||||
const initial = typeof window !== 'undefined' ? loadSession() : null
|
||||
return {
|
||||
token: initial?.token ?? null,
|
||||
user: initial?.user ?? null,
|
||||
companyId: initial?.companyId ?? null,
|
||||
|
||||
setAuth: (token, user) => {
|
||||
const payload = decodeJwtPayload(token)
|
||||
saveSession(token, user, payload.companyId)
|
||||
set({ token, user, companyId: payload.companyId })
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
clearSession()
|
||||
set({ token: null, user: null, companyId: null })
|
||||
},
|
||||
}))
|
||||
}})
|
||||
|
||||
50
packages/admin/src/stores/theme.store.ts
Normal file
50
packages/admin/src/stores/theme.store.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { create } from 'zustand'
|
||||
import { applyThemeColors, getThemeByName } from '@/lib/themes'
|
||||
|
||||
type Mode = 'light' | 'dark' | 'system'
|
||||
|
||||
interface ThemeState {
|
||||
mode: Mode
|
||||
colorTheme: string
|
||||
setMode: (mode: Mode) => void
|
||||
setColorTheme: (name: string) => void
|
||||
}
|
||||
|
||||
function isDarkMode(mode: Mode): boolean {
|
||||
return mode === 'dark' || (mode === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
}
|
||||
|
||||
function apply(mode: Mode, colorTheme: string) {
|
||||
const dark = isDarkMode(mode)
|
||||
document.documentElement.classList.toggle('dark', dark)
|
||||
const theme = getThemeByName(colorTheme)
|
||||
applyThemeColors(dark ? theme.dark : theme.light)
|
||||
}
|
||||
|
||||
export const useThemeStore = create<ThemeState>((set) => {
|
||||
const initialMode = (typeof window !== 'undefined' ? localStorage.getItem('forte-mode') as Mode : null) ?? 'system'
|
||||
const initialColor = (typeof window !== 'undefined' ? localStorage.getItem('forte-color-theme') : null) ?? 'slate'
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
apply(initialMode, initialColor)
|
||||
}
|
||||
|
||||
return {
|
||||
mode: initialMode,
|
||||
colorTheme: initialColor,
|
||||
|
||||
setMode: (mode) => {
|
||||
localStorage.setItem('forte-mode', mode)
|
||||
const colorTheme = useThemeStore.getState().colorTheme
|
||||
apply(mode, colorTheme)
|
||||
set({ mode })
|
||||
},
|
||||
|
||||
setColorTheme: (name) => {
|
||||
localStorage.setItem('forte-color-theme', name)
|
||||
const mode = useThemeStore.getState().mode
|
||||
apply(mode, name)
|
||||
set({ colorTheme: name })
|
||||
},
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user