From 07ceef12946e0fe8b4f922b7c305493abb387b1d Mon Sep 17 00:00:00 2001 From: kcar Date: Sun, 31 May 2026 11:00:55 +0000 Subject: [PATCH] refactor(theme): hoist ThemeProvider to App.tsx root --- src/App.tsx | 44 ++++-- src/AppRoutes.tsx | 4 +- src/pages/Header.tsx | 10 ++ .../components/shared/BasePanel.tsx | 135 +++++++----------- 4 files changed, 98 insertions(+), 95 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f017170..969d435 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,7 @@ import { BrowserRouter } from 'react-router-dom'; -import { ThemeProvider } from '@mui/material/styles'; +import { useMemo } from 'react'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { useTLDraw } from './contexts/TLDrawContext'; import { theme } from './services/themeService'; import { AuthProvider } from './contexts/AuthContext'; import { TLDrawProvider } from './contexts/TLDrawContext'; @@ -8,21 +10,43 @@ import AppRoutes from './AppRoutes'; import { ErrorBoundary } from './components/ErrorBoundary'; import React from 'react'; -const App = React.memo(() => ( - - - +const App = React.memo(() => { + const { tldrawPreferences } = useTLDraw(); + + const prefersDarkMode = + typeof window !== 'undefined' && + window.matchMedia('(prefers-color-scheme: dark)').matches; + + const appTheme = useMemo(() => { + let mode: 'light' | 'dark'; + + if (tldrawPreferences?.colorScheme === 'system') { + mode = prefersDarkMode ? 'dark' : 'light'; + } else { + mode = tldrawPreferences?.colorScheme === 'dark' ? 'dark' : 'light'; + } + + return createTheme({ + palette: { mode }, + }); + }, [tldrawPreferences?.colorScheme, prefersDarkMode]); + + return ( + + - + + + - - - -)); + + + ); +}); App.displayName = import.meta.env.VITE_APP_NAME; diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx index 85665b7..0b2edda 100644 --- a/src/AppRoutes.tsx +++ b/src/AppRoutes.tsx @@ -169,8 +169,8 @@ const AppRoutes: React.FC = () => { )} - {/* Fallback route - use different NotFound pages based on auth state */} - : } /> + {/* Fallback route - authenticated users go to dashboard, unauthenticated see public 404 */} + : } /> ); diff --git a/src/pages/Header.tsx b/src/pages/Header.tsx index 2d0e9c8..93b0c21 100644 --- a/src/pages/Header.tsx +++ b/src/pages/Header.tsx @@ -7,6 +7,7 @@ import { Typography, IconButton, Box, + Chip, useTheme, Menu, MenuItem, @@ -126,6 +127,15 @@ const Header: React.FC = () => { + {isAuthenticated && bootstrapData?.onboarding?.next_step && bootstrapData.onboarding.next_step !== 'ready' && ( + navigate('/dashboard')} + sx={{ cursor: 'pointer', display: { xs: 'none', sm: 'flex' } }} + /> + )} {isAuthenticated && ( ({ '& .MuiSvgIcon-root': { fontSize: '1.25rem', color: 'inherit', - } + }, })); const StyledMenuItem = styled(MenuItem)(() => ({ @@ -106,7 +104,7 @@ const StyledMenuItem = styled(MenuItem)(() => ({ backgroundColor: 'var(--color-hover)', '& .MuiListItemIcon-root': { color: 'var(--color-selected)', - } + }, }, '& .MuiListItemIcon-root': { color: 'var(--color-text)', @@ -114,8 +112,8 @@ const StyledMenuItem = styled(MenuItem)(() => ({ transition: 'color 200ms ease', '& .MuiSvgIcon-root': { fontSize: '1.25rem', - } - } + }, + }, })); export const BasePanel: React.FC = ({ @@ -130,45 +128,35 @@ export const BasePanel: React.FC = ({ }) => { const location = useLocation(); const { tldrawPreferences } = useTLDraw(); - const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); + const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; const [menuAnchorEl, setMenuAnchorEl] = React.useState(null); - - // Create a dynamic theme based on TLDraw preferences - const theme = useMemo(() => { - let mode: 'light' | 'dark'; - - if (tldrawPreferences?.colorScheme === 'system') { - mode = prefersDarkMode ? 'dark' : 'light'; - } else { - mode = tldrawPreferences?.colorScheme === 'dark' ? 'dark' : 'light'; - } - return createTheme({ - palette: { - mode, - }, - }); + // Mui theme object from root provider + const computedTheme = useTheme(); + const accentColor = useMemo(() => { + if (tldrawPreferences?.colorScheme === 'dark') return '#e2e8f0'; + if (tldrawPreferences?.colorScheme === 'system') return prefersDarkMode ? '#e2e8f0' : '#1e293b'; + return '#1e293b'; }, [tldrawPreferences?.colorScheme, prefersDarkMode]); const isExamMarkerRoute = location.pathname === '/exam-marker'; const availablePanels = isExamMarkerRoute ? PANEL_TYPES.examMarker : PANEL_TYPES.default; - + const [currentPanelType, setCurrentPanelType] = React.useState( - isExamMarkerRoute ? 'exam-marker' : initialPanelType + isExamMarkerRoute ? 'exam-marker' : initialPanelType, ); - - // Use controlled state if provided, otherwise use internal state + const [internalIsExpanded, setInternalIsExpanded] = React.useState(true); const [internalIsPinned, setInternalIsPinned] = React.useState(true); - + const isExpanded = controlledIsExpanded ?? internalIsExpanded; const isPinned = controlledIsPinned ?? internalIsPinned; - + const handleExpandedChange = (expanded: boolean) => { setInternalIsExpanded(expanded); onExpandedChange?.(expanded); }; - + const handlePinToggle = () => { const newPinned = !isPinned; setInternalIsPinned(newPinned); @@ -178,16 +166,11 @@ export const BasePanel: React.FC = ({ const panelRef = useRef(null); const dimensions = PANEL_DIMENSIONS[currentPanelType as keyof typeof PANEL_DIMENSIONS]; - // Handle click outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { - // Don't close if pinned if (isPinned) return; - // Check if click is outside panel const isClickOutside = panelRef.current && !panelRef.current.contains(event.target as Node); - - // Check if click is not on a panel-related element const target = event.target as HTMLElement; const isPanelElement = target.closest('.panel-root, .panel-handle, .tlui-button'); @@ -288,13 +271,11 @@ export const BasePanel: React.FC = ({ } }; - // Handle menu button click const handleMenuClick = (event: React.MouseEvent) => { setMenuAnchorEl(event.currentTarget); onMenuOpenChange(true); }; - // Handle menu close const handleMenuClose = () => { setMenuAnchorEl(null); onMenuOpenChange(false); @@ -303,7 +284,7 @@ export const BasePanel: React.FC = ({ return ( <> {!isExpanded && ( -
handleExpandedChange(true)} onTouchEnd={(e) => { @@ -316,7 +297,7 @@ export const BasePanel: React.FC = ({ )} {isExpanded && ( -
= ({ }} >
- - } - startIcon={getIconForPanel(currentPanelType)} - > - {availablePanels.find(p => p.id === currentPanelType)?.label} - + } + startIcon={getIconForPanel(currentPanelType)} + > + {availablePanels.find((p) => p.id === currentPanelType)?.label} + - - {[...availablePanels] - .sort((a, b) => a.order - b.order) - .map(type => ( + + {[...availablePanels] + .sort((a, b) => a.order - b.order) + .map((type) => ( { @@ -359,42 +339,31 @@ export const BasePanel: React.FC = ({ }} selected={currentPanelType === type.id} > - - {getIconForPanel(type.id as PanelType)} - - {getIconForPanel(type.id as PanelType)} + ))} - - +
- + {isPinned ? : }
- -
- {renderCurrentPanel()} -
-
+
{renderCurrentPanel()}
)} ); -}; \ No newline at end of file +};