Skip to content

Commit 2caedcb

Browse files
committed
feat: site-wide dark mode and improved debate timer
1 parent 160807f commit 2caedcb

6 files changed

Lines changed: 499 additions & 131 deletions

File tree

‎src/App.tsx‎

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { AppLayout } from "@/components/layout/AppLayout";
99
import { AuthProvider } from "@/lib/supabase/AuthProvider";
1010
import { RequireAuth } from "@/components/auth/RequireAuth";
1111
import { OnboardingTour } from "@/components/features/onboarding/OnboardingTour";
12+
import { ThemeProvider } from "@/components/ThemeProvider";
1213
import { Loader2 } from "lucide-react";
1314

1415
// Lazy load all pages for code splitting
@@ -40,43 +41,45 @@ const PageLoader = () => (
4041
const App = () => (
4142
<QueryClientProvider client={queryClient}>
4243
<AuthProvider>
43-
<TooltipProvider>
44-
<Toaster />
45-
<Sonner />
46-
<BrowserRouter>
47-
<Suspense fallback={<PageLoader />}>
48-
<Routes>
49-
{/* Public Routes */}
50-
<Route path="/" element={<LandingPage />} />
51-
<Route path="/login" element={<LoginPage />} />
52-
<Route path="/share/:code" element={<SharedPage />} />
44+
<ThemeProvider>
45+
<TooltipProvider>
46+
<Toaster />
47+
<Sonner />
48+
<BrowserRouter>
49+
<Suspense fallback={<PageLoader />}>
50+
<Routes>
51+
{/* Public Routes */}
52+
<Route path="/" element={<LandingPage />} />
53+
<Route path="/login" element={<LoginPage />} />
54+
<Route path="/share/:code" element={<SharedPage />} />
5355

54-
{/* Protected App Routes */}
55-
<Route
56-
element={
57-
<RequireAuth>
58-
<OnboardingTour />
59-
<AppLayout />
60-
</RequireAuth>
61-
}
62-
>
63-
<Route path="/app" element={<GeneratePage />} />
64-
<Route path="/app/attack" element={<AttackPage />} />
65-
<Route path="/app/brainstorm" element={<BrainstormPage />} />
66-
<Route path="/app/factoids" element={<FactoidsPage />} />
67-
<Route path="/app/ask" element={<AskPage />} />
68-
<Route path="/app/history" element={<HistoryPage />} />
69-
<Route path="/app/library" element={<LibraryPage />} />
70-
<Route path="/app/timer" element={<TimerPage />} />
71-
<Route path="/app/flowchart" element={<FlowchartPage />} />
72-
<Route path="/app/settings" element={<SettingsPage />} />
73-
<Route path="/app/admin" element={<AdminPage />} />
74-
</Route>
75-
<Route path="*" element={<NotFound />} />
76-
</Routes>
77-
</Suspense>
78-
</BrowserRouter>
79-
</TooltipProvider>
56+
{/* Protected App Routes */}
57+
<Route
58+
element={
59+
<RequireAuth>
60+
<OnboardingTour />
61+
<AppLayout />
62+
</RequireAuth>
63+
}
64+
>
65+
<Route path="/app" element={<GeneratePage />} />
66+
<Route path="/app/attack" element={<AttackPage />} />
67+
<Route path="/app/brainstorm" element={<BrainstormPage />} />
68+
<Route path="/app/factoids" element={<FactoidsPage />} />
69+
<Route path="/app/ask" element={<AskPage />} />
70+
<Route path="/app/history" element={<HistoryPage />} />
71+
<Route path="/app/library" element={<LibraryPage />} />
72+
<Route path="/app/timer" element={<TimerPage />} />
73+
<Route path="/app/flowchart" element={<FlowchartPage />} />
74+
<Route path="/app/settings" element={<SettingsPage />} />
75+
<Route path="/app/admin" element={<AdminPage />} />
76+
</Route>
77+
<Route path="*" element={<NotFound />} />
78+
</Routes>
79+
</Suspense>
80+
</BrowserRouter>
81+
</TooltipProvider>
82+
</ThemeProvider>
8083
</AuthProvider>
8184
</QueryClientProvider>
8285
);

‎src/components/ThemeProvider.tsx‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// src/components/ThemeProvider.tsx
2+
import { useEffect } from 'react';
3+
import { useSettingsStore } from '@/lib/stores/settings-store';
4+
5+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
6+
const theme = useSettingsStore((state) => state.theme);
7+
8+
useEffect(() => {
9+
const root = window.document.documentElement;
10+
11+
// Remove existing theme classes
12+
root.classList.remove('light', 'dark');
13+
14+
if (theme === 'system') {
15+
// Check system preference
16+
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
17+
? 'dark'
18+
: 'light';
19+
root.classList.add(systemTheme);
20+
} else {
21+
root.classList.add(theme);
22+
}
23+
}, [theme]);
24+
25+
// Listen for system theme changes when in 'system' mode
26+
useEffect(() => {
27+
if (theme !== 'system') return;
28+
29+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
30+
31+
const handleChange = (e: MediaQueryListEvent) => {
32+
const root = window.document.documentElement;
33+
root.classList.remove('light', 'dark');
34+
root.classList.add(e.matches ? 'dark' : 'light');
35+
};
36+
37+
mediaQuery.addEventListener('change', handleChange);
38+
return () => mediaQuery.removeEventListener('change', handleChange);
39+
}, [theme]);
40+
41+
return <>{children}</>;
42+
}
43+
44+
// Hook for components that need current resolved theme
45+
export function useResolvedTheme(): 'light' | 'dark' {
46+
const theme = useSettingsStore((state) => state.theme);
47+
48+
if (theme === 'system') {
49+
if (typeof window !== 'undefined') {
50+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
51+
}
52+
return 'light';
53+
}
54+
55+
return theme;
56+
}

‎src/components/ThemeToggle.tsx‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Moon, Sun, Laptop } from "lucide-react";
2+
import { Button } from "@/components/ui/button";
3+
import { useSettingsStore } from "@/lib/stores/settings-store";
4+
5+
export function ThemeToggle() {
6+
const { theme, setTheme } = useSettingsStore();
7+
8+
return (
9+
<Button
10+
variant="ghost"
11+
size="icon"
12+
className="text-muted-foreground hover:text-foreground"
13+
onClick={() => {
14+
if (theme === 'light') setTheme('dark');
15+
else if (theme === 'dark') setTheme('system');
16+
else setTheme('light');
17+
}}
18+
title={`Current theme: ${theme}. Click to cycle.`}
19+
>
20+
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
21+
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
22+
<span className="sr-only">Toggle theme</span>
23+
</Button>
24+
);
25+
}

‎src/components/layout/AppLayout.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { NavLink } from '@/components/NavLink';
1919
import { Button } from '@/components/ui/button';
2020
import { cn } from '@/lib/utils';
21+
import { ThemeToggle } from '@/components/ThemeToggle';
2122

2223
const navItems = [
2324
{ path: '/app', label: 'Generate', icon: Sparkles, description: 'Build arguments' },
@@ -70,6 +71,7 @@ export function AppLayout() {
7071
</nav>
7172

7273
<div className="flex items-center gap-2">
74+
<ThemeToggle />
7375
<NavLink to="/app/settings">
7476
<Button variant="ghost" size="icon" className="text-muted-foreground hover:text-foreground" aria-label="Settings">
7577
<Settings className="h-5 w-5" />

0 commit comments

Comments
 (0)