fix: layout loading settings
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import { useTenantStore } from '@KTXC/stores/tenantStore'
|
|||||||
import { useUserStore } from '@KTXC/stores/userStore'
|
import { useUserStore } from '@KTXC/stores/userStore'
|
||||||
import { useL10nStore } from '@KTXC/stores/l10nStore'
|
import { useL10nStore } from '@KTXC/stores/l10nStore'
|
||||||
import { useThemeStore } from '@KTXC/stores/themeStore'
|
import { useThemeStore } from '@KTXC/stores/themeStore'
|
||||||
|
import { useLayoutStore } from '@KTXC/stores/layoutStore'
|
||||||
import { i18n } from '@KTXC/l10n/runtime'
|
import { i18n } from '@KTXC/l10n/runtime'
|
||||||
import { fetchWrapper } from '@KTXC/utils/helpers/fetch-wrapper'
|
import { fetchWrapper } from '@KTXC/utils/helpers/fetch-wrapper'
|
||||||
import { FetchError } from '@KTXC/utils/helpers/fetch-wrapper-core'
|
import { FetchError } from '@KTXC/utils/helpers/fetch-wrapper-core'
|
||||||
@@ -43,12 +44,14 @@ globalWindow.Pinia = PiniaLib as unknown
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const l10nStore = useL10nStore();
|
const l10nStore = useL10nStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
|
const layoutStore = useLayoutStore();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = await fetchWrapper.get('/init');
|
const payload = await fetchWrapper.get('/init');
|
||||||
moduleStore.init(payload?.modules ?? {});
|
moduleStore.init(payload?.modules ?? {});
|
||||||
tenantStore.init(payload?.tenant ?? null);
|
tenantStore.init(payload?.tenant ?? null);
|
||||||
userStore.init(payload?.user ?? {});
|
userStore.init(payload?.user ?? {});
|
||||||
|
layoutStore.hydrateFromSettings();
|
||||||
themeStore.boot();
|
themeStore.boot();
|
||||||
|
|
||||||
// Resolve locale and load catalogs before modules boot
|
// Resolve locale and load catalogs before modules boot
|
||||||
|
|||||||
@@ -13,29 +13,59 @@ export const useLayoutStore = defineStore('layout', () => {
|
|||||||
// Sidebar state - initialize from settings or defaults
|
// Sidebar state - initialize from settings or defaults
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const tenantStore = useTenantStore();
|
const tenantStore = useTenantStore();
|
||||||
const sidebarDrawer = ref(userStore.getSetting('sidebar_drawer') ?? true);
|
|
||||||
const miniSidebar = ref(userStore.getSetting('mini_sidebar') ?? false);
|
function booleanSetting(key: string, fallback: boolean): boolean {
|
||||||
|
const value = userStore.getSetting(key);
|
||||||
|
return typeof value === 'boolean' ? value : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function themeSetting(value: unknown): ThemeMode | null {
|
||||||
|
return value === 'light' || value === 'dark' || value === 'system' ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialTheme(): ThemeMode {
|
||||||
|
return (
|
||||||
|
themeSetting(userStore.getSetting('theme')) ??
|
||||||
|
themeSetting(tenantStore.getSetting('theme_default_mode')) ??
|
||||||
|
'light'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidebarDrawer = ref<boolean>(booleanSetting('sidebar_drawer', true));
|
||||||
|
const miniSidebar = ref<boolean>(booleanSetting('mini_sidebar', false));
|
||||||
const menuMode = ref<MenuMode>('apps');
|
const menuMode = ref<MenuMode>('apps');
|
||||||
|
|
||||||
// Theme mode - user choice, falling back to the tenant default
|
// Theme mode - user choice, falling back to the tenant default
|
||||||
const theme = ref<ThemeMode>(
|
const theme = ref<ThemeMode>(initialTheme());
|
||||||
(userStore.getSetting('theme') as ThemeMode | null) ??
|
|
||||||
(tenantStore.getSetting('theme_default_mode') as ThemeMode | null) ??
|
let hydratingFromSettings = false;
|
||||||
'light'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Watch and sync sidebar state to settings
|
// Watch and sync sidebar state to settings
|
||||||
watch(sidebarDrawer, (value) => {
|
watch(sidebarDrawer, (value) => {
|
||||||
|
if (hydratingFromSettings) return;
|
||||||
userStore.setSetting('sidebar_drawer', value);
|
userStore.setSetting('sidebar_drawer', value);
|
||||||
});
|
}, { flush: 'sync' });
|
||||||
|
|
||||||
watch(miniSidebar, (value) => {
|
watch(miniSidebar, (value) => {
|
||||||
|
if (hydratingFromSettings) return;
|
||||||
userStore.setSetting('mini_sidebar', value);
|
userStore.setSetting('mini_sidebar', value);
|
||||||
});
|
}, { flush: 'sync' });
|
||||||
|
|
||||||
watch(theme, (value) => {
|
watch(theme, (value) => {
|
||||||
|
if (hydratingFromSettings) return;
|
||||||
userStore.setSetting('theme', value);
|
userStore.setSetting('theme', value);
|
||||||
});
|
}, { flush: 'sync' });
|
||||||
|
|
||||||
|
function hydrateFromSettings() {
|
||||||
|
hydratingFromSettings = true;
|
||||||
|
try {
|
||||||
|
sidebarDrawer.value = booleanSetting('sidebar_drawer', true);
|
||||||
|
miniSidebar.value = booleanSetting('mini_sidebar', false);
|
||||||
|
theme.value = initialTheme();
|
||||||
|
} finally {
|
||||||
|
hydratingFromSettings = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
function toggleSidebarDrawer() {
|
function toggleSidebarDrawer() {
|
||||||
@@ -75,6 +105,7 @@ export const useLayoutStore = defineStore('layout', () => {
|
|||||||
setMiniSidebar,
|
setMiniSidebar,
|
||||||
setMenuMode,
|
setMenuMode,
|
||||||
toggleMenuMode,
|
toggleMenuMode,
|
||||||
setTheme
|
setTheme,
|
||||||
|
hydrateFromSettings,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { createPinia, setActivePinia } from 'pinia'
|
||||||
|
import { useLayoutStore } from '@KTXC/stores/layoutStore'
|
||||||
|
import { useTenantStore } from '@KTXC/stores/tenantStore'
|
||||||
|
import { useUserStore } from '@KTXC/stores/userStore'
|
||||||
|
|
||||||
|
const userServiceMock = vi.hoisted(() => ({
|
||||||
|
updateProfile: vi.fn(() => Promise.resolve()),
|
||||||
|
updateSettings: vi.fn(() => Promise.resolve()),
|
||||||
|
flushAll: vi.fn(() => Promise.resolve()),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@KTXC/services/user/userService', () => ({
|
||||||
|
userService: userServiceMock,
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('layoutStore', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const storage = new Map<string, string>()
|
||||||
|
vi.stubGlobal('localStorage', {
|
||||||
|
getItem: vi.fn((key: string) => storage.get(key) ?? null),
|
||||||
|
setItem: vi.fn((key: string, value: string) => storage.set(key, value)),
|
||||||
|
removeItem: vi.fn((key: string) => storage.delete(key)),
|
||||||
|
clear: vi.fn(() => storage.clear()),
|
||||||
|
})
|
||||||
|
setActivePinia(createPinia())
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hydrates initial layout preferences after settings are loaded', () => {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const tenantStore = useTenantStore()
|
||||||
|
|
||||||
|
const layoutStore = useLayoutStore()
|
||||||
|
expect(layoutStore.sidebarDrawer).toBe(true)
|
||||||
|
expect(layoutStore.miniSidebar).toBe(false)
|
||||||
|
expect(layoutStore.theme).toBe('light')
|
||||||
|
|
||||||
|
tenantStore.init({
|
||||||
|
id: 'tenant',
|
||||||
|
domain: 'example.test',
|
||||||
|
label: 'Example',
|
||||||
|
settings: {
|
||||||
|
theme_default_mode: 'dark',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
userStore.init({
|
||||||
|
settings: {
|
||||||
|
sidebar_drawer: false,
|
||||||
|
mini_sidebar: true,
|
||||||
|
theme: 'system',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
layoutStore.hydrateFromSettings()
|
||||||
|
|
||||||
|
expect(layoutStore.sidebarDrawer).toBe(false)
|
||||||
|
expect(layoutStore.miniSidebar).toBe(true)
|
||||||
|
expect(layoutStore.theme).toBe('system')
|
||||||
|
expect(userServiceMock.updateSettings).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('continues to persist user-driven layout changes', () => {
|
||||||
|
const layoutStore = useLayoutStore()
|
||||||
|
|
||||||
|
layoutStore.toggleSidebarDrawer()
|
||||||
|
layoutStore.setMiniSidebar(true)
|
||||||
|
layoutStore.setTheme('dark')
|
||||||
|
|
||||||
|
expect(userServiceMock.updateSettings).toHaveBeenCalledWith({ sidebar_drawer: false })
|
||||||
|
expect(userServiceMock.updateSettings).toHaveBeenCalledWith({ mini_sidebar: true })
|
||||||
|
expect(userServiceMock.updateSettings).toHaveBeenCalledWith({ theme: 'dark' })
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user