feat: theming
All checks were successful
JS Unit Tests / test (pull_request) Successful in 17s
Build Test / build (pull_request) Successful in 19s
PHP Unit Tests / test (pull_request) Successful in 54s

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-02-22 21:25:26 -05:00
parent 6975800ce5
commit b68ac538ce
7 changed files with 331 additions and 45 deletions

View File

@@ -1,6 +1,5 @@
import { ref, watch } from 'vue';
import { defineStore } from 'pinia';
import config from '@KTXC/config';
import { useUserStore } from './userStore';
export type MenuMode = 'apps' | 'user-settings' | 'admin-settings';
@@ -9,15 +8,15 @@ export const useLayoutStore = defineStore('layout', () => {
// Loading state
const isLoading = ref(false);
// Sidebar state - initialize from settings or config
// Sidebar state - initialize from settings or defaults
const userStore = useUserStore();
const sidebarDrawer = ref(userStore.getSetting('sidebar_drawer') ?? config.Sidebar_drawer);
const miniSidebar = ref(userStore.getSetting('mini_sidebar') ?? config.mini_sidebar);
const sidebarDrawer = ref(userStore.getSetting('sidebar_drawer') ?? true);
const miniSidebar = ref(userStore.getSetting('mini_sidebar') ?? false);
const menuMode = ref<MenuMode>('apps');
// Theme state - initialize from settings or config
const theme = ref(userStore.getSetting('theme') ?? config.actTheme);
const font = ref(userStore.getSetting('font') ?? config.fontTheme);
// Theme state - initialize from settings or defaults
const theme = ref(userStore.getSetting('theme') ?? 'light');
const font = ref(userStore.getSetting('font') ?? 'Public sans');
// Watch and sync sidebar state to settings
watch(sidebarDrawer, (value) => {

View File

@@ -1,4 +1,6 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { fetchWrapper } from '@KTXC/utils/helpers/fetch-wrapper';
export interface TenantState {
id: string | null;
@@ -6,22 +8,117 @@ export interface TenantState {
label: string | null;
}
export const useTenantStore = defineStore('tenantStore', {
state: () => ({
tenant: null as TenantState | null,
}),
actions: {
init(tenant: Partial<TenantState> | null) {
this.tenant = tenant
? {
id: tenant.id ?? null,
domain: tenant.domain ?? null,
label: tenant.label ?? null,
}
: null;
},
reset() {
this.tenant = null;
},
},
export interface TenantData extends TenantState {
settings?: Record<string, unknown>;
}
// Flush pending settings writes before the page unloads
if (typeof window !== 'undefined') {
window.addEventListener('beforeunload', () => {
useTenantStore().flushSettings();
});
}
export const useTenantStore = defineStore('tenantStore', () => {
// =========================================================================
// State
// =========================================================================
const tenant = ref<TenantState | null>(null);
const settings = ref<Record<string, unknown>>({});
// Pending batch for debounced writes
let _pendingSettings: Record<string, unknown> = {};
let _debounceTimer: ReturnType<typeof setTimeout> | null = null;
// =========================================================================
// Getters
// =========================================================================
function getSetting(key: string): unknown {
return settings.value[key] ?? null;
}
// =========================================================================
// Settings actions
// =========================================================================
function setSetting(key: string, value: unknown): void {
settings.value[key] = value;
// Batch writes with a 500 ms debounce — same pattern as userService
_pendingSettings[key] = value;
if (_debounceTimer !== null) {
clearTimeout(_debounceTimer);
}
_debounceTimer = setTimeout(() => {
_flush();
}, 500);
}
async function _flush(): Promise<void> {
if (Object.keys(_pendingSettings).length === 0) return;
const payload = { ..._pendingSettings };
_pendingSettings = {};
_debounceTimer = null;
try {
await fetchWrapper.patch('/tenant/settings', { data: payload });
} catch (error) {
console.error('Failed to save tenant settings:', error);
}
}
/** Force-flush any pending settings writes (called on beforeunload). */
async function flushSettings(): Promise<void> {
if (_debounceTimer !== null) {
clearTimeout(_debounceTimer);
_debounceTimer = null;
}
await _flush();
}
// =========================================================================
// Initialise from /init endpoint
// =========================================================================
function init(tenantData: Partial<TenantData> | null): void {
tenant.value = tenantData
? {
id: tenantData.id ?? null,
domain: tenantData.domain ?? null,
label: tenantData.label ?? null,
}
: null;
settings.value = (tenantData?.settings as Record<string, unknown>) ?? {};
}
function reset(): void {
tenant.value = null;
settings.value = {};
_pendingSettings = {};
if (_debounceTimer !== null) {
clearTimeout(_debounceTimer);
_debounceTimer = null;
}
}
return {
// State
tenant,
settings,
// Getters
getSetting,
// Actions
setSetting,
flushSettings,
init,
reset,
};
});