mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: default theme setting not persisting across sessions (#98)
Three disconnected systems caused the theme to never apply from server settings: the DEFAULT_THEME env var was parsed but never seeded to the database, the settings store ignored defaultTheme from the API, and the settings dialog wrote to the DB without updating the active theme store. - Seed DEFAULT_THEME and DEFAULT_LOCALE env vars into the settings table on first startup (ensureDefaultSettings in index.ts) - Add applyServerDefault() to theme store that applies the server's default theme only when the user hasn't made an explicit choice - Extract defaultTheme from the settings API response and apply it on fresh sessions (no localStorage preference) - Apply theme immediately when admin saves settings - Allow "system" as a valid DEFAULT_THEME env var value
This commit is contained in:
@@ -32,6 +32,7 @@ import { apiDelete, apiGet, apiPost, apiPut, clearToken, formatHeaders } from "@
|
||||
import { cn, copyToClipboard } from "@/lib/utils";
|
||||
import { useAnalyticsStore } from "@/stores/analytics-store";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import { useThemeStore } from "@/stores/theme-store";
|
||||
import { OtterLogo } from "../common/otter-logo";
|
||||
import { AiFeaturesSection } from "./ai-features-section";
|
||||
|
||||
@@ -367,6 +368,10 @@ function SystemSection() {
|
||||
setSaveMsg(null);
|
||||
try {
|
||||
await apiPut("/v1/settings", settings);
|
||||
if (settings.defaultTheme) {
|
||||
const theme = settings.defaultTheme as "light" | "dark" | "system";
|
||||
useThemeStore.getState().setTheme(theme);
|
||||
}
|
||||
setSaveMsg("Settings saved.");
|
||||
} catch {
|
||||
setSaveMsg("Failed to save settings.");
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { create } from "zustand";
|
||||
import { apiGet } from "@/lib/api";
|
||||
import { useThemeStore } from "./theme-store";
|
||||
|
||||
type Theme = "light" | "dark" | "system";
|
||||
|
||||
interface SettingsState {
|
||||
disabledTools: string[];
|
||||
experimentalEnabled: boolean;
|
||||
defaultToolView: "sidebar" | "fullscreen";
|
||||
defaultTheme: Theme;
|
||||
loaded: boolean;
|
||||
fetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
const VALID_THEMES = new Set(["light", "dark", "system"]);
|
||||
|
||||
export const useSettingsStore = create<SettingsState>((set, get) => ({
|
||||
disabledTools: [],
|
||||
experimentalEnabled: false,
|
||||
defaultToolView: "sidebar",
|
||||
defaultTheme: "light",
|
||||
loaded: false,
|
||||
|
||||
fetch: async () => {
|
||||
@@ -22,12 +29,19 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
|
||||
settings: Record<string, string>;
|
||||
}>("/v1/settings");
|
||||
|
||||
const defaultTheme = VALID_THEMES.has(data.settings.defaultTheme)
|
||||
? (data.settings.defaultTheme as Theme)
|
||||
: "light";
|
||||
|
||||
set({
|
||||
disabledTools: data.settings.disabledTools ? JSON.parse(data.settings.disabledTools) : [],
|
||||
experimentalEnabled: data.settings.enableExperimentalTools === "true",
|
||||
defaultToolView: data.settings.defaultToolView === "fullscreen" ? "fullscreen" : "sidebar",
|
||||
defaultTheme,
|
||||
loaded: true,
|
||||
});
|
||||
|
||||
useThemeStore.getState().applyServerDefault(defaultTheme);
|
||||
} catch {
|
||||
set({ loaded: true });
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@ import { persist } from "zustand/middleware";
|
||||
|
||||
type Theme = "light" | "dark" | "system";
|
||||
|
||||
const USER_THEME_KEY = "snapotter-theme-user-set";
|
||||
|
||||
interface ThemeStore {
|
||||
theme: Theme;
|
||||
setTheme: (theme: Theme) => void;
|
||||
resolvedTheme: "light" | "dark";
|
||||
applyServerDefault: (theme: Theme) => void;
|
||||
}
|
||||
|
||||
function getSystemTheme(): "light" | "dark" {
|
||||
@@ -14,14 +17,25 @@ function getSystemTheme(): "light" | "dark" {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
function applyTheme(theme: Theme): "light" | "dark" {
|
||||
const resolved = theme === "system" ? getSystemTheme() : theme;
|
||||
document.documentElement.classList.toggle("dark", resolved === "dark");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
export const useThemeStore = create<ThemeStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
theme: "light" as Theme,
|
||||
resolvedTheme: "light" as const,
|
||||
setTheme: (theme) => {
|
||||
const resolved = theme === "system" ? getSystemTheme() : theme;
|
||||
document.documentElement.classList.toggle("dark", resolved === "dark");
|
||||
const resolved = applyTheme(theme);
|
||||
localStorage.setItem(USER_THEME_KEY, "1");
|
||||
set({ theme, resolvedTheme: resolved });
|
||||
},
|
||||
applyServerDefault: (theme) => {
|
||||
if (localStorage.getItem(USER_THEME_KEY)) return;
|
||||
const resolved = applyTheme(theme);
|
||||
set({ theme, resolvedTheme: resolved });
|
||||
},
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user