🎯 Objective
Implement a dark/light mode toggle with persistent theme preference, resolving the hardcoded light-mode colors in dashboard and wallet components.
📁 Files to Create
| Action |
File Path |
Description |
| Create |
frontend/src/context/ThemeContext.tsx |
Theme provider with dark/light/system modes |
| Create |
frontend/src/hooks/useTheme.ts |
Hook for theme access and toggle |
📁 Files to Modify
| Action |
File Path |
Description |
| Modify |
frontend/src/App.tsx |
Wrap with ThemeProvider |
| Modify |
frontend/src/components/layout/TopNav.tsx |
Add theme toggle button |
| Modify |
frontend/src/styles/global.css |
Add light theme CSS variables |
| Modify |
frontend/src/components/dashboard/KpiCard.module.css |
Use CSS variables |
| Modify |
frontend/src/components/dashboard/NetworkHealthBadge.module.css |
Use CSS variables |
| Modify |
frontend/src/components/dashboard/RecentTasksTable.module.css |
Use CSS variables |
| Modify |
frontend/src/components/wallet/SendXLMForm.module.css |
Use CSS variables |
| Modify |
frontend/src/components/wallet/TransactionTable.module.css |
Use CSS variables |
| Modify |
frontend/src/pages/WalletPage.module.css |
Use CSS variables |
| Modify |
frontend/src/components/agents/TaskSubmissionForm.tsx |
Use CSS variables |
🔍 Current State
The app is dark-mode only. Many components use hardcoded light-mode colors that clash with the dark layout shell (see issue #152).
✅ Expected Implementation
1. Create ThemeContext.tsx
import { createContext, useContext, useState, useEffect } from 'react';
type Theme = 'dark' | 'light' | 'system';
interface ThemeContextValue {
theme: Theme;
resolvedTheme: 'dark' | 'light';
setTheme: (theme: Theme) => void;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = useState<Theme>(() => {
return (localStorage.getItem('theme') as Theme) || 'system';
});
const [resolvedTheme, setResolvedTheme] = useState<'dark' | 'light'>('dark');
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const resolve = () => {
if (theme === 'system') {
setResolvedTheme(mediaQuery.matches ? 'dark' : 'light');
} else {
setResolvedTheme(theme);
}
};
resolve();
mediaQuery.addEventListener('change', resolve);
return () => mediaQuery.removeEventListener('change', resolve);
}, [theme]);
useEffect(() => {
document.documentElement.setAttribute('data-theme', resolvedTheme);
localStorage.setItem('theme', theme);
}, [theme, resolvedTheme]);
const setTheme = (t: Theme) => setThemeState(t);
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}
2. Update global.css — Add Light Theme Variables
:root[data-theme="dark"] {
--bg-primary: #0A0E14;
--bg-secondary: #1A1F2E;
--text-primary: #E2E8F0;
--text-secondary: #94A3B8;
--border-color: #2D3748;
}
:root[data-theme="light"] {
--bg-primary: #FFFFFF;
--bg-secondary: #F8FAFC;
--text-primary: #0F172A;
--text-secondary: #475569;
--border-color: #E2E8F0;
}
3. Add Toggle Button (TopNav.tsx)
import { useTheme } from '../../context/ThemeContext';
import { Sun, Moon, Monitor } from 'lucide-react';
function ThemeToggle() {
const { theme, setTheme } = useTheme();
const cycle = () => {
const next = theme === 'dark' ? 'light' : theme === 'light' ? 'system' : 'dark';
setTheme(next);
};
return (
<button onClick={cycle} aria-label="Toggle theme">
{theme === 'dark' && <Moon size={18} />}
{theme === 'light' && <Sun size={18} />}
{theme === 'system' && <Monitor size={18} />}
</button>
);
}
4. Update All Components (from issue #152)
Replace all hardcoded colors with CSS variable references in:
KpiCard.module.css
NetworkHealthBadge.module.css
RecentTasksTable.module.css
SendXLMForm.module.css
TransactionTable.module.css
WalletPage.module.css
TaskSubmissionForm.tsx (inline styles)
📁 Reference Files
| File Path |
Purpose |
frontend/src/App.tsx |
Wrap with ThemeProvider |
frontend/src/components/layout/TopNav.tsx |
Add toggle button |
frontend/src/styles/global.css |
CSS variables |
frontend/src/components/common/ErrorBoundary.tsx |
Reference for context pattern |
| All CSS module files listed above |
Replace hardcoded colors |
✅ Acceptance Criteria
🎯 Objective
Implement a dark/light mode toggle with persistent theme preference, resolving the hardcoded light-mode colors in dashboard and wallet components.
📁 Files to Create
frontend/src/context/ThemeContext.tsxfrontend/src/hooks/useTheme.ts📁 Files to Modify
frontend/src/App.tsxThemeProviderfrontend/src/components/layout/TopNav.tsxfrontend/src/styles/global.cssfrontend/src/components/dashboard/KpiCard.module.cssfrontend/src/components/dashboard/NetworkHealthBadge.module.cssfrontend/src/components/dashboard/RecentTasksTable.module.cssfrontend/src/components/wallet/SendXLMForm.module.cssfrontend/src/components/wallet/TransactionTable.module.cssfrontend/src/pages/WalletPage.module.cssfrontend/src/components/agents/TaskSubmissionForm.tsx🔍 Current State
The app is dark-mode only. Many components use hardcoded light-mode colors that clash with the dark layout shell (see issue #152).
✅ Expected Implementation
1. Create
ThemeContext.tsx2. Update
global.css— Add Light Theme Variables3. Add Toggle Button (TopNav.tsx)
4. Update All Components (from issue #152)
Replace all hardcoded colors with CSS variable references in:
KpiCard.module.cssNetworkHealthBadge.module.cssRecentTasksTable.module.cssSendXLMForm.module.cssTransactionTable.module.cssWalletPage.module.cssTaskSubmissionForm.tsx(inline styles)📁 Reference Files
frontend/src/App.tsxfrontend/src/components/layout/TopNav.tsxfrontend/src/styles/global.cssfrontend/src/components/common/ErrorBoundary.tsx✅ Acceptance Criteria
ThemeContext.tsxwith dark/light/system modesuseTheme()hookglobal.cssTopNav.tsxprefers-color-schemefor system modeTaskSubmissionForm.tsxinline styles