diff --git a/src/App.tsx b/src/App.tsx
index 0ae5832..d322e3f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,63 +1,69 @@
import { ApolloProvider } from '@apollo/client/react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
-import { ConfigProvider, theme } from 'antd';
+import { ConfigProvider } from 'antd';
import { client } from './lib/apollo-client';
import { useAuth } from './hooks/useAuth';
+import { ThemeProvider, useTheme } from './hooks/useTheme';
import { LoginPage } from './pages/LoginPage';
import { MainLayout } from './components/MainLayout';
import { ProtectedRoute } from './components/ProtectedRoute';
-function App() {
+function AppContent() {
const { isAuthenticated } = useAuth();
+ const { themeConfig } = useTheme();
+
+ return (
+
+
+
+ :
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+ {/* Legacy dashboard route - redirect to new views structure */}
+ }
+ />
+ }
+ />
+
+
+
+ );
+}
+function App() {
return (
-
-
-
- :
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
- {/* Legacy dashboard route - redirect to new views structure */}
- }
- />
- }
- />
-
-
-
+
+
+
);
}
diff --git a/src/components/MainLayout.tsx b/src/components/MainLayout.tsx
index 8a1e0d9..03920f2 100644
--- a/src/components/MainLayout.tsx
+++ b/src/components/MainLayout.tsx
@@ -9,6 +9,7 @@ import {
} from '@ant-design/icons';
import { useNavigate, useParams } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
+import { ThemeSelector } from './ThemeSelector';
import { viewRegistry, getViewCategories, getViewsByCategory, getTopLevelViews } from '../registry/viewRegistry';
const { Header, Sider, Content } = Layout;
@@ -188,6 +189,7 @@ export const MainLayout: React.FC = () => {
+
} />
{user?.name || user?.email || 'User'}
diff --git a/src/components/ThemeSelector.tsx b/src/components/ThemeSelector.tsx
new file mode 100644
index 0000000..6428c53
--- /dev/null
+++ b/src/components/ThemeSelector.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { Select, Space, Tooltip } from 'antd';
+import { BgColorsOutlined } from '@ant-design/icons';
+import { useTheme, type ThemeType } from '../hooks/useTheme';
+
+const { Option } = Select;
+
+export const ThemeSelector: React.FC = () => {
+ const { currentTheme, changeTheme, getAvailableThemes } = useTheme();
+ const themes = getAvailableThemes();
+
+ const handleThemeChange = (value: ThemeType) => {
+ changeTheme(value);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/hooks/useTheme.tsx b/src/hooks/useTheme.tsx
new file mode 100644
index 0000000..96dda2b
--- /dev/null
+++ b/src/hooks/useTheme.tsx
@@ -0,0 +1,109 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import { theme } from 'antd';
+
+export type ThemeType = 'light' | 'dark' | 'compact';
+
+export interface ThemeConfig {
+ type: ThemeType;
+ name: string;
+ algorithm: any;
+ token: {
+ colorPrimary: string;
+ borderRadius: number;
+ colorBgContainer: string;
+ colorBgElevated?: string;
+ colorText?: string;
+ colorTextSecondary?: string;
+ };
+}
+
+const themeConfigs: Record = {
+ light: {
+ type: 'light',
+ name: 'Light',
+ algorithm: theme.defaultAlgorithm,
+ token: {
+ colorPrimary: '#1890ff',
+ borderRadius: 8,
+ colorBgContainer: '#ffffff',
+ },
+ },
+ dark: {
+ type: 'dark',
+ name: 'Dark',
+ algorithm: theme.darkAlgorithm,
+ token: {
+ colorPrimary: '#1890ff',
+ borderRadius: 8,
+ colorBgContainer: '#141414',
+ colorBgElevated: '#1f1f1f',
+ },
+ },
+ compact: {
+ type: 'compact',
+ name: 'Compact',
+ algorithm: [theme.defaultAlgorithm, theme.compactAlgorithm],
+ token: {
+ colorPrimary: '#722ed1',
+ borderRadius: 4,
+ colorBgContainer: '#ffffff',
+ },
+ },
+};
+
+interface ThemeContextType {
+ currentTheme: ThemeType;
+ changeTheme: (newTheme: ThemeType) => void;
+ getThemeConfig: (themeType?: ThemeType) => ThemeConfig;
+ getAvailableThemes: () => ThemeConfig[];
+ themeConfig: ThemeConfig;
+}
+
+const ThemeContext = createContext(undefined);
+
+export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [currentTheme, setCurrentTheme] = useState('light');
+
+ useEffect(() => {
+ // Load theme from localStorage on mount
+ const savedTheme = localStorage.getItem('app-theme') as ThemeType;
+ if (savedTheme && themeConfigs[savedTheme]) {
+ setCurrentTheme(savedTheme);
+ }
+ }, []);
+
+ const changeTheme = (newTheme: ThemeType) => {
+ setCurrentTheme(newTheme);
+ localStorage.setItem('app-theme', newTheme);
+ };
+
+ const getThemeConfig = (themeType: ThemeType = currentTheme): ThemeConfig => {
+ return themeConfigs[themeType];
+ };
+
+ const getAvailableThemes = (): ThemeConfig[] => {
+ return Object.values(themeConfigs);
+ };
+
+ const value = {
+ currentTheme,
+ changeTheme,
+ getThemeConfig,
+ getAvailableThemes,
+ themeConfig: getThemeConfig(),
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error('useTheme must be used within a ThemeProvider');
+ }
+ return context;
+};
\ No newline at end of file