From f72092124cc8f122ea9e0c13f9863a1e0165a77c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:29:42 +0000 Subject: [PATCH 1/3] Initial plan From b3bab5cb2c1ee2e319f6cfdd905c7f5d1d268829 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:39:04 +0000 Subject: [PATCH 2/3] Implement complete theme selection functionality Co-authored-by: sebasmoyano <5084534+sebasmoyano@users.noreply.github.com> --- src/App.tsx | 12 ++--- src/components/MainLayout.tsx | 2 + src/components/ThemeSelector.tsx | 41 +++++++++++++++ src/hooks/useTheme.ts | 85 ++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 src/components/ThemeSelector.tsx create mode 100644 src/hooks/useTheme.ts diff --git a/src/App.tsx b/src/App.tsx index 0ae5832..57bcca3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,25 +1,23 @@ 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 { useTheme } from './hooks/useTheme'; import { LoginPage } from './pages/LoginPage'; import { MainLayout } from './components/MainLayout'; import { ProtectedRoute } from './components/ProtectedRoute'; function App() { const { isAuthenticated } = useAuth(); + const { themeConfig } = useTheme(); return ( 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.ts b/src/hooks/useTheme.ts new file mode 100644 index 0000000..d38980c --- /dev/null +++ b/src/hooks/useTheme.ts @@ -0,0 +1,85 @@ +import { 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', + }, + }, +}; + +export const useTheme = () => { + 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); + }; + + return { + currentTheme, + changeTheme, + getThemeConfig, + getAvailableThemes, + themeConfig: getThemeConfig(), + }; +}; \ No newline at end of file From ac8e92bde2036485ae18dbfa77c772200c2d9658 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:21:03 +0000 Subject: [PATCH 3/3] Fix theme switching by implementing React Context for shared state Co-authored-by: sebasmoyano <5084534+sebasmoyano@users.noreply.github.com> --- src/App.tsx | 96 +++++++++++++------------ src/hooks/{useTheme.ts => useTheme.tsx} | 30 +++++++- 2 files changed, 79 insertions(+), 47 deletions(-) rename src/hooks/{useTheme.ts => useTheme.tsx} (71%) diff --git a/src/App.tsx b/src/App.tsx index 57bcca3..d322e3f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,59 +3,67 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-d import { ConfigProvider } from 'antd'; import { client } from './lib/apollo-client'; import { useAuth } from './hooks/useAuth'; -import { useTheme } from './hooks/useTheme'; +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/hooks/useTheme.ts b/src/hooks/useTheme.tsx similarity index 71% rename from src/hooks/useTheme.ts rename to src/hooks/useTheme.tsx index d38980c..96dda2b 100644 --- a/src/hooks/useTheme.ts +++ b/src/hooks/useTheme.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import React, { createContext, useContext, useState, useEffect } from 'react'; import { theme } from 'antd'; export type ThemeType = 'light' | 'dark' | 'compact'; @@ -51,7 +51,17 @@ const themeConfigs: Record = { }, }; -export const useTheme = () => { +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(() => { @@ -75,11 +85,25 @@ export const useTheme = () => { return Object.values(themeConfigs); }; - return { + 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