From 279edf8b6fec0069e85ed653d6d1c548c2b18346 Mon Sep 17 00:00:00 2001 From: Omid Saadat Date: Wed, 29 Oct 2025 01:18:27 +0100 Subject: [PATCH 1/2] feat(web): improve theme system clarity and fix login page theme selection - Add descriptive labels to distinguish workspace vs user theme settings - Fix login page theme selection not reflecting in dropdown - Add ThemeInfoCard component explaining theme system - Enhance ThemeSelect with effective theme indicators - Improve UX by clarifying theme scope and priority Resolves theme confusion between System > General > Theme and System > Basic > Edit > Theme --- web/src/components/AuthFooter.tsx | 16 ++++++++- .../Settings/PreferencesSection.tsx | 8 ++++- .../components/Settings/WorkspaceSection.tsx | 5 ++- web/src/components/ThemeInfoCard.tsx | 33 +++++++++++++++++++ web/src/components/ThemeSelect.tsx | 19 +++++++++-- 5 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 web/src/components/ThemeInfoCard.tsx diff --git a/web/src/components/AuthFooter.tsx b/web/src/components/AuthFooter.tsx index 6553af5bca724..0ad7b4ff4f740 100644 --- a/web/src/components/AuthFooter.tsx +++ b/web/src/components/AuthFooter.tsx @@ -1,6 +1,8 @@ import { observer } from "mobx-react-lite"; +import { useState } from "react"; import { cn } from "@/lib/utils"; import { workspaceStore } from "@/store"; +import { loadTheme } from "@/utils/theme"; import LocaleSelect from "./LocaleSelect"; import ThemeSelect from "./ThemeSelect"; @@ -9,10 +11,22 @@ interface Props { } const AuthFooter = observer(({ className }: Props) => { + // Local state for login page theme since we can't persist to server + const [localTheme, setLocalTheme] = useState(workspaceStore.state.theme || "default"); + + const handleThemeChange = (theme: string) => { + // Update local state + setLocalTheme(theme); + // Update workspace store for immediate UI feedback + workspaceStore.state.setPartial({ theme }); + // Apply theme to DOM + loadTheme(theme); + }; + return (
workspaceStore.state.setPartial({ locale })} /> - workspaceStore.state.setPartial({ theme })} /> +
); }); diff --git a/web/src/components/Settings/PreferencesSection.tsx b/web/src/components/Settings/PreferencesSection.tsx index 978343e741651..a24d7eeebbb12 100644 --- a/web/src/components/Settings/PreferencesSection.tsx +++ b/web/src/components/Settings/PreferencesSection.tsx @@ -8,6 +8,7 @@ import { useTranslate } from "@/utils/i18n"; import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo"; import LocaleSelect from "../LocaleSelect"; import ThemeSelect from "../ThemeSelect"; +import ThemeInfoCard from "../ThemeInfoCard"; import VisibilityIcon from "../VisibilityIcon"; import WebhookSection from "./WebhookSection"; @@ -47,10 +48,15 @@ const PreferencesSection = observer(() => {
- {t("setting.preference-section.theme")} +
+ {t("setting.preference-section.theme")} + Your personal theme preference (overrides default) +
+ +

{t("setting.preference")}

diff --git a/web/src/components/Settings/WorkspaceSection.tsx b/web/src/components/Settings/WorkspaceSection.tsx index 6d0830812512d..d44052a91efcc 100644 --- a/web/src/components/Settings/WorkspaceSection.tsx +++ b/web/src/components/Settings/WorkspaceSection.tsx @@ -81,7 +81,10 @@ const WorkspaceSection = observer(() => {

{t("setting.system-section.title")}

- Theme +
+ Default Theme + Sets the default theme for all users +
updatePartialSetting({ theme: value })} diff --git a/web/src/components/ThemeInfoCard.tsx b/web/src/components/ThemeInfoCard.tsx new file mode 100644 index 0000000000000..a762c303b6e13 --- /dev/null +++ b/web/src/components/ThemeInfoCard.tsx @@ -0,0 +1,33 @@ +import { observer } from "mobx-react-lite"; +import { Info } from "lucide-react"; +import { userStore, workspaceStore } from "@/store"; + +const ThemeInfoCard = observer(() => { + const userTheme = userStore.state.userGeneralSetting?.theme; + const workspaceTheme = workspaceStore.state.theme || "default"; + const effectiveTheme = userTheme || workspaceTheme; + + return ( +
+
+ +
+

Theme System

+
+

Current effective theme: {effectiveTheme}

+

Workspace default: {workspaceTheme}

+ {userTheme && ( +

Your preference: {userTheme}

+ )} +

+ Your personal theme preference overrides the workspace default. + If you haven't set a personal preference, the workspace default is used. +

+
+
+
+
+ ); +}); + +export default ThemeInfoCard; \ No newline at end of file diff --git a/web/src/components/ThemeSelect.tsx b/web/src/components/ThemeSelect.tsx index 3e0a1a4e22304..24d69eb7cf59f 100644 --- a/web/src/components/ThemeSelect.tsx +++ b/web/src/components/ThemeSelect.tsx @@ -1,12 +1,14 @@ +import { observer } from "mobx-react-lite"; import { Moon, Palette, Sun, Wallpaper } from "lucide-react"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { workspaceStore } from "@/store"; +import { workspaceStore, userStore } from "@/store"; import { THEME_OPTIONS } from "@/utils/theme"; interface ThemeSelectProps { value?: string; onValueChange?: (theme: string) => void; className?: string; + showEffectiveTheme?: boolean; } const THEME_ICONS: Record = { @@ -16,8 +18,13 @@ const THEME_ICONS: Record = { whitewall: , }; -const ThemeSelect = ({ value, onValueChange, className }: ThemeSelectProps = {}) => { +const ThemeSelect = observer(({ value, onValueChange, className, showEffectiveTheme = false }: ThemeSelectProps = {}) => { const currentTheme = value || workspaceStore.state.theme || "default"; + + // Calculate effective theme (user preference overrides workspace default) + const effectiveTheme = userStore.state.userGeneralSetting?.theme || workspaceStore.state.theme || "default"; + + const displayTheme = showEffectiveTheme ? effectiveTheme : currentTheme; const handleThemeChange = (newTheme: Theme) => { if (onValueChange) { @@ -32,6 +39,9 @@ const ThemeSelect = ({ value, onValueChange, className }: ThemeSelectProps = {})
+ {showEffectiveTheme && effectiveTheme !== currentTheme && ( + (effective: {effectiveTheme}) + )}
@@ -40,12 +50,15 @@ const ThemeSelect = ({ value, onValueChange, className }: ThemeSelectProps = {})
{THEME_ICONS[option.value]} {option.label} + {showEffectiveTheme && option.value === effectiveTheme && ( + + )}
))}
); -}; +}); export default ThemeSelect; From a284053831f17524722195aab390c9c3a21ecd2e Mon Sep 17 00:00:00 2001 From: Omid Saadat Date: Wed, 29 Oct 2025 01:34:47 +0100 Subject: [PATCH 2/2] refactor(web): remove ThemeInfoCard from preferences section - Remove detailed theme system explanation from user preferences - Keep simple descriptive label for user theme preference - Maintain clean UI without technical details --- .../Settings/PreferencesSection.tsx | 3 -- web/src/components/ThemeInfoCard.tsx | 33 ------------------- 2 files changed, 36 deletions(-) delete mode 100644 web/src/components/ThemeInfoCard.tsx diff --git a/web/src/components/Settings/PreferencesSection.tsx b/web/src/components/Settings/PreferencesSection.tsx index a24d7eeebbb12..df237e023f124 100644 --- a/web/src/components/Settings/PreferencesSection.tsx +++ b/web/src/components/Settings/PreferencesSection.tsx @@ -8,7 +8,6 @@ import { useTranslate } from "@/utils/i18n"; import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo"; import LocaleSelect from "../LocaleSelect"; import ThemeSelect from "../ThemeSelect"; -import ThemeInfoCard from "../ThemeInfoCard"; import VisibilityIcon from "../VisibilityIcon"; import WebhookSection from "./WebhookSection"; @@ -55,8 +54,6 @@ const PreferencesSection = observer(() => {
- -

{t("setting.preference")}

diff --git a/web/src/components/ThemeInfoCard.tsx b/web/src/components/ThemeInfoCard.tsx deleted file mode 100644 index a762c303b6e13..0000000000000 --- a/web/src/components/ThemeInfoCard.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { observer } from "mobx-react-lite"; -import { Info } from "lucide-react"; -import { userStore, workspaceStore } from "@/store"; - -const ThemeInfoCard = observer(() => { - const userTheme = userStore.state.userGeneralSetting?.theme; - const workspaceTheme = workspaceStore.state.theme || "default"; - const effectiveTheme = userTheme || workspaceTheme; - - return ( -
-
- -
-

Theme System

-
-

Current effective theme: {effectiveTheme}

-

Workspace default: {workspaceTheme}

- {userTheme && ( -

Your preference: {userTheme}

- )} -

- Your personal theme preference overrides the workspace default. - If you haven't set a personal preference, the workspace default is used. -

-
-
-
-
- ); -}); - -export default ThemeInfoCard; \ No newline at end of file