From 47593748837256e8a463752b0205334a454b2063 Mon Sep 17 00:00:00 2001 From: olsch01 Date: Sun, 8 Mar 2026 19:36:11 -0400 Subject: [PATCH] feat: add dark mode with persistent user preference Add dark mode support using Mantine's built-in color scheme system, persisted via a new Zustand preferences store. Includes a quick toggle in the app header and an enabled switch in User Preferences. Also removes the "AI Health Scores" title from the dashboard to reclaim vertical space. Co-Authored-By: Claude Opus 4.6 --- frontend/src/components/layout/AppLayout.tsx | 16 +++++++++++- frontend/src/main.tsx | 15 ++++++++--- .../src/pages/dashboard/DashboardPage.tsx | 1 - .../pages/preferences/UserPreferencesPage.tsx | 9 +++++-- frontend/src/stores/preferencesStore.ts | 26 +++++++++++++++++++ 5 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 frontend/src/stores/preferencesStore.ts diff --git a/frontend/src/components/layout/AppLayout.tsx b/frontend/src/components/layout/AppLayout.tsx index cb6fd4a..9fb4476 100644 --- a/frontend/src/components/layout/AppLayout.tsx +++ b/frontend/src/components/layout/AppLayout.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { AppShell, Burger, Group, Text, Menu, UnstyledButton, Avatar, Alert, Button } from '@mantine/core'; +import { AppShell, Burger, Group, Text, Menu, UnstyledButton, Avatar, Alert, Button, ActionIcon, Tooltip } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { IconLogout, @@ -9,9 +9,12 @@ import { IconUserCog, IconUsersGroup, IconEyeOff, + IconSun, + IconMoon, } from '@tabler/icons-react'; import { Outlet, useNavigate, useLocation } from 'react-router-dom'; import { useAuthStore } from '../../stores/authStore'; +import { usePreferencesStore } from '../../stores/preferencesStore'; import { Sidebar } from './Sidebar'; import { AppTour } from '../onboarding/AppTour'; import { OnboardingWizard } from '../onboarding/OnboardingWizard'; @@ -20,6 +23,7 @@ import logoSrc from '../../assets/logo.svg'; export function AppLayout() { const [opened, { toggle, close }] = useDisclosure(); const { user, currentOrg, logout, impersonationOriginal, stopImpersonation } = useAuthStore(); + const { colorScheme, toggleColorScheme } = usePreferencesStore(); const navigate = useNavigate(); const location = useLocation(); const isImpersonating = !!impersonationOriginal; @@ -108,6 +112,16 @@ export function AppLayout() { {currentOrg && ( {currentOrg.name} )} + + + {colorScheme === 'dark' ? : } + + diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index d50ec5a..609a2d6 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -10,6 +10,7 @@ import '@mantine/dates/styles.css'; import '@mantine/notifications/styles.css'; import { App } from './App'; import { theme } from './theme/theme'; +import { usePreferencesStore } from './stores/preferencesStore'; const queryClient = new QueryClient({ defaultOptions: { @@ -21,9 +22,11 @@ const queryClient = new QueryClient({ }, }); -ReactDOM.createRoot(document.getElementById('root')!).render( - - +function Root() { + const colorScheme = usePreferencesStore((s) => s.colorScheme); + + return ( + @@ -33,5 +36,11 @@ ReactDOM.createRoot(document.getElementById('root')!).render( + ); +} + +ReactDOM.createRoot(document.getElementById('root')!).render( + + , ); diff --git a/frontend/src/pages/dashboard/DashboardPage.tsx b/frontend/src/pages/dashboard/DashboardPage.tsx index 1ec5837..c5546a2 100644 --- a/frontend/src/pages/dashboard/DashboardPage.tsx +++ b/frontend/src/pages/dashboard/DashboardPage.tsx @@ -414,7 +414,6 @@ export function DashboardPage() {
) : ( <> - AI Health Scores @@ -66,7 +68,10 @@ export function UserPreferencesPage() { Dark Mode Switch to dark color theme - +
@@ -76,7 +81,7 @@ export function UserPreferencesPage() { - Display preferences coming in a future release + More display preferences coming in a future release diff --git a/frontend/src/stores/preferencesStore.ts b/frontend/src/stores/preferencesStore.ts new file mode 100644 index 0000000..f2160fc --- /dev/null +++ b/frontend/src/stores/preferencesStore.ts @@ -0,0 +1,26 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; + +type ColorScheme = 'light' | 'dark'; + +interface PreferencesState { + colorScheme: ColorScheme; + toggleColorScheme: () => void; + setColorScheme: (scheme: ColorScheme) => void; +} + +export const usePreferencesStore = create()( + persist( + (set) => ({ + colorScheme: 'light', + toggleColorScheme: () => + set((state) => ({ + colorScheme: state.colorScheme === 'light' ? 'dark' : 'light', + })), + setColorScheme: (scheme) => set({ colorScheme: scheme }), + }), + { + name: 'ledgeriq-preferences', + }, + ), +);