Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 54 additions & 48 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ConfigProvider
theme={{
algorithm: themeConfig.algorithm,
token: themeConfig.token,
}}
>
<Router>
<Routes>
<Route
path="/login"
element={
isAuthenticated ? <Navigate to="/views/api-integration" replace /> : <LoginPage />
}
/>
<Route
path="/views/:viewId"
element={
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
}
/>
<Route
path="/views"
element={
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
}
/>
{/* Legacy dashboard route - redirect to new views structure */}
<Route
path="/dashboard"
element={<Navigate to="/views/dashboard" replace />}
/>
<Route
path="/"
element={<Navigate to={isAuthenticated ? "/views/api-integration" : "/login"} replace />}
/>
</Routes>
</Router>
</ConfigProvider>
);
}

function App() {
return (
<ApolloProvider client={client}>
<ConfigProvider
theme={{
algorithm: theme.defaultAlgorithm,
token: {
colorPrimary: '#1890ff',
borderRadius: 8,
colorBgContainer: '#ffffff',
},
}}
>
<Router>
<Routes>
<Route
path="/login"
element={
isAuthenticated ? <Navigate to="/views/api-integration" replace /> : <LoginPage />
}
/>
<Route
path="/views/:viewId"
element={
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
}
/>
<Route
path="/views"
element={
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
}
/>
{/* Legacy dashboard route - redirect to new views structure */}
<Route
path="/dashboard"
element={<Navigate to="/views/dashboard" replace />}
/>
<Route
path="/"
element={<Navigate to={isAuthenticated ? "/views/api-integration" : "/login"} replace />}
/>
</Routes>
</Router>
</ConfigProvider>
<ThemeProvider>
<AppContent />
</ThemeProvider>
</ApolloProvider>
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -188,6 +189,7 @@ export const MainLayout: React.FC = () => {
</Badge>
</Space>
<Space>
<ThemeSelector />
<Avatar size="small" icon={<UserOutlined />} />
<Text style={{ color: 'white' }}>
{user?.name || user?.email || 'User'}
Expand Down
41 changes: 41 additions & 0 deletions src/components/ThemeSelector.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Tooltip title="Change theme">
<Space>
<BgColorsOutlined style={{ color: 'white', fontSize: '16px' }} />
<Select
value={currentTheme}
onChange={handleThemeChange}
style={{
minWidth: 100,
}}
variant="borderless"
size="small"
dropdownStyle={{
minWidth: 120,
}}
>
{themes.map(theme => (
<Option key={theme.type} value={theme.type}>
{theme.name}
</Option>
))}
</Select>
</Space>
</Tooltip>
);
};
109 changes: 109 additions & 0 deletions src/hooks/useTheme.tsx
Original file line number Diff line number Diff line change
@@ -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<ThemeType, ThemeConfig> = {
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<ThemeContextType | undefined>(undefined);

export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [currentTheme, setCurrentTheme] = useState<ThemeType>('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 (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
};

export const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};