From 279edf8b6fec0069e85ed653d6d1c548c2b18346 Mon Sep 17 00:00:00 2001 From: Omid Saadat Date: Wed, 29 Oct 2025 01:18:27 +0100 Subject: [PATCH] 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 6553af5bc..0ad7b4ff4 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 978343e74..a24d7eeeb 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 6d0830812..d44052a91 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 000000000..a762c303b --- /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 3e0a1a4e2..24d69eb7c 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;