Additional work on Main menu, and history components

This commit is contained in:
corey@blaishome.online
2025-02-15 15:03:10 -05:00
parent d7e3fe3b2b
commit 0d05370cb9
9 changed files with 384 additions and 486 deletions

View File

@@ -1,94 +1,17 @@
import React, { useState, useMemo, useEffect } from 'react'; // File: src/App.js
import { CssBaseline, Container, IconButton } from '@mui/material'; import React from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { ScoreProvider } from './context/ScoreContext'; import { ScoreProvider } from './context/ScoreContext';
import GameSetup from './components/GameSetup'; import AppRouter from './AppRouter';
import ScoreTracker from './components/ScoreTracker'; import { ThemeProvider } from './context/ThemeContext';
import GameSummary from './components/GameSummary';
import { Brightness7, Brightness4 } from '@mui/icons-material';
function App() { function App() {
// Initialize theme from localStorage or default to 'light'
const [mode, setMode] = useState(() => {
try {
const savedMode = localStorage.getItem('themeMode');
return savedMode || 'light';
} catch (e) {
return 'light';
}
});
const [gameStarted, setGameStarted] = useState(() => {
try {
const savedGame = localStorage.getItem('archeryScores');
if (savedGame) {
const parsedGame = JSON.parse(savedGame);
return parsedGame.currentGame && parsedGame.currentGame.gameType !== null;
}
} catch {
return false;
}
return false;
});
// Save theme preference to localStorage
useEffect(() => {
localStorage.setItem('themeMode', mode);
}, [mode]);
const theme = useMemo(
() =>
createTheme({
palette: {
mode,
},
}),
[mode],
);
const toggleTheme = () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
};
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider>
<ScoreProvider> <ScoreProvider>
<CssBaseline /> <AppRouter />
<Container
maxWidth="lg"
sx={{
minHeight: '100vh',
bgcolor: 'background.default',
color: 'text.primary',
pb: 4
}}
>
<IconButton
onClick={toggleTheme}
color="inherit"
sx={{ position: 'absolute', top: 16, right: 16 }}
>
{mode === 'dark' ? <Brightness7 /> : <Brightness4 />}
</IconButton>
<Router>
<Routes>
<Route
path="/"
element={
!gameStarted
? <GameSetup onGameStart={() => setGameStarted(true)} />
: <ScoreTracker />
}
/>
<Route path="/summary" element={<GameSummary />} />
</Routes>
</Router>
</Container>
</ScoreProvider> </ScoreProvider>
</ThemeProvider> </ThemeProvider>
); );
} }
export default App; export default App;

61
asc/src/AppRouter.js Normal file
View File

@@ -0,0 +1,61 @@
// File: src/AppRouter.js
import React, { useState } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { Container, IconButton } from '@mui/material';
import { Brightness7, Brightness4 } from '@mui/icons-material';
import GameSetup from './components/GameSetup';
import ScoreTracker from './components/ScoreTracker';
import GameSummary from './components/GameSummary';
import { useTheme } from './context/ThemeContext';
function AppRouter() {
const { mode, toggleTheme } = useTheme();
const [gameStarted, setGameStarted] = useState(() => {
try {
const savedGame = localStorage.getItem('archeryScores');
if (savedGame) {
const parsedGame = JSON.parse(savedGame);
return parsedGame.currentGame && parsedGame.currentGame.gameType !== null;
}
} catch {
return false;
}
return false;
});
return (
<Container
maxWidth="lg"
sx={{
minHeight: '100vh',
bgcolor: 'background.default',
color: 'text.primary',
pb: 4
}}
>
<IconButton
onClick={toggleTheme}
color="inherit"
sx={{ position: 'absolute', top: 16, right: 16 }}
>
{mode === 'dark' ? <Brightness7 /> : <Brightness4 />}
</IconButton>
<Router>
<Routes>
<Route
path="/"
element={
!gameStarted
? <GameSetup onGameStart={() => setGameStarted(true)} />
: <ScoreTracker />
}
/>
<Route path="/summary" element={<GameSummary />} />
</Routes>
</Router>
</Container>
);
}
export default AppRouter;

View File

@@ -1,4 +1,4 @@
import React from 'react'; import React, { useEffect } from 'react';
import { useScore, ACTIONS } from '../context/ScoreContext'; import { useScore, ACTIONS } from '../context/ScoreContext';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Button, Typography, Box, Grid } from '@mui/material'; import { Button, Typography, Box, Grid } from '@mui/material';
@@ -7,6 +7,13 @@ const GameSummary = () => {
const { state, dispatch } = useScore(); const { state, dispatch } = useScore();
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => {
// Save league games when summary component mounts
if (state.currentGame.isLeague) {
dispatch({ type: ACTIONS.SAVE_GAME });
}
}, [dispatch, state.currentGame.isLeague]); // Added missing dependencies
const handleReturnToMenu = () => { const handleReturnToMenu = () => {
dispatch({ type: ACTIONS.RESET_GAME }); dispatch({ type: ACTIONS.RESET_GAME });
navigate('/'); navigate('/');
@@ -18,7 +25,31 @@ const GameSummary = () => {
Game Summary Game Summary
</Typography> </Typography>
{/* Your existing summary content here */} <Grid container spacing={3} sx={{ maxWidth: 600, mx: 'auto' }}>
<Grid item xs={12}>
<Typography variant="h6" align="center">
{state.currentGame.gameType} Round {state.currentGame.isLeague ? '(League)' : '(Practice)'}
</Typography>
</Grid>
<Grid item xs={12} sm={6}>
<Typography align="center">
Total Score: {state.currentGame.totalScore}
</Typography>
</Grid>
<Grid item xs={12} sm={6}>
<Typography align="center">
Total Bullseyes: {state.currentGame.totalBullseyes}
</Typography>
</Grid>
<Grid item xs={12}>
<Typography align="center">
Average per Round: {(state.currentGame.totalScore / state.currentGame.rounds.length).toFixed(1)}
</Typography>
</Grid>
</Grid>
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'center' }}> <Box sx={{ mt: 4, display: 'flex', justifyContent: 'center' }}>
<Button <Button

View File

@@ -0,0 +1,76 @@
import React from 'react';
import { useScore } from '../context/ScoreContext';
import { useNavigate } from 'react-router-dom';
import { Box, Typography, Button, Card, CardContent, Grid } from '@mui/material';
const History = () => {
const { state } = useScore();
const navigate = useNavigate();
// Group games by date for better organization
const groupedGames = state.history.reduce((groups, game) => {
const date = new Date(game.date).toLocaleDateString();
if (!groups[date]) {
groups[date] = [];
}
groups[date].push(game);
return groups;
}, {});
return (
<Box sx={{ p: 4 }}>
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
mb: 4
}}>
<Typography variant="h4">League History</Typography>
<Button
variant="outlined"
onClick={() => navigate('/')}
>
Back to Menu
</Button>
</Box>
{Object.keys(groupedGames).length > 0 ? (
Object.entries(groupedGames)
.sort((a, b) => new Date(b[0]) - new Date(a[0])) // Sort dates newest first
.map(([date, games]) => (
<Box key={date} sx={{ mb: 4 }}>
<Typography variant="h6" sx={{ mb: 2 }}>{date}</Typography>
<Grid container spacing={2}>
{games.map((game, index) => (
<Grid item xs={12} sm={6} md={4} key={index}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
{game.gameType} Round
</Typography>
<Typography>
Total Score: {game.totalScore}
</Typography>
<Typography>
Bullseyes: {game.totalBullseyes}
</Typography>
<Typography>
Average: {(game.totalScore / game.rounds.length).toFixed(1)}
</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Box>
))
) : (
<Typography variant="body1" align="center">
No league games recorded yet.
</Typography>
)}
</Box>
);
};
export default History;

View File

@@ -0,0 +1,107 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useScore, ACTIONS } from '../context/ScoreContext';
import { Button, Box, Typography, Grid } from '@mui/material';
const MainMenu = () => {
const navigate = useNavigate();
const { dispatch } = useScore();
const handleGameStart = (gameType, isLeague) => {
dispatch({
type: ACTIONS.START_GAME,
payload: {
gameType: gameType,
isLeague: isLeague
}
});
navigate('/game');
};
return (
<Box sx={{
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 4
}}>
<Typography variant="h3" gutterBottom align="center">
Archery Score Card
</Typography>
{/* League Section */}
<Box sx={{ width: '100%', maxWidth: 400 }}>
<Typography variant="h5" gutterBottom align="center">
League
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Button
variant="contained"
fullWidth
onClick={() => handleGameStart('450', true)}
sx={{ height: 60 }}
>
450 Round
</Button>
</Grid>
<Grid item xs={6}>
<Button
variant="contained"
fullWidth
onClick={() => handleGameStart('300', true)}
sx={{ height: 60 }}
>
300 Round
</Button>
</Grid>
</Grid>
</Box>
{/* Practice Section */}
<Box sx={{ width: '100%', maxWidth: 400 }}>
<Typography variant="h5" gutterBottom align="center">
Practice
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Button
variant="outlined"
fullWidth
onClick={() => handleGameStart('450', false)}
sx={{ height: 60 }}
>
450 Round
</Button>
</Grid>
<Grid item xs={6}>
<Button
variant="outlined"
fullWidth
onClick={() => handleGameStart('300', false)}
sx={{ height: 60 }}
>
300 Round
</Button>
</Grid>
</Grid>
</Box>
{/* History Button */}
<Box sx={{ width: '100%', maxWidth: 400 }}>
<Button
variant="contained"
color="secondary"
fullWidth
onClick={() => navigate('/history')}
sx={{ height: 60 }}
>
View League History
</Button>
</Box>
</Box>
);
};
export default MainMenu;

View File

@@ -1,207 +0,0 @@
import React, { useState } from 'react';
import { useScore, ACTIONS } from '../context/ScoreContext';
import { Button, Grid, Typography, TextField, Box } from '@mui/material';
import { useNavigate } from 'react-router-dom';
const ScoreTracker = () => {
const { state, dispatch } = useScore();
const navigate = useNavigate();
const gameType = state.currentGame.gameType;
const maxArrowsPerRound = gameType === '450' ? 3 : 5;
const maxScore = gameType === '450' ? 10 : 5;
const maxRounds = gameType === '450' ? 16 : 12;
const [arrowScores, setArrowScores] = useState(Array(maxArrowsPerRound).fill(''));
const handleMainMenu = () => {
if (window.confirm('Are you sure you want to return to the main menu? Current game progress will be lost.')) {
dispatch({ type: ACTIONS.RESET_GAME });
navigate('/');
}
};
const handleScoreChange = (index, value) => {
const updatedScores = [...arrowScores];
updatedScores[index] = value;
setArrowScores(updatedScores);
};
const handleAddRound = () => {
if (state.currentGame.rounds.length >= maxRounds - 1) { // Check if this is the last round
const valid = arrowScores.slice(0, maxArrowsPerRound).every(score =>
(score >= 0 && score <= maxScore) || score.toUpperCase() === 'X'
);
if (!valid) {
alert(`Please enter valid scores between 0-${maxScore} or X for bullseyes.`);
return;
}
const roundArrows = arrowScores.slice(0, maxArrowsPerRound).map((score) => {
const arrowScore = score.toUpperCase() === 'X' ? maxScore : parseInt(score, 10);
return {
score: arrowScore,
isBullseye: score.toUpperCase() === 'X',
};
});
dispatch({
type: ACTIONS.ADD_ROUND,
payload: {
roundIndex: state.currentGame.rounds.length,
arrows: roundArrows,
},
});
setArrowScores(Array(maxArrowsPerRound).fill(''));
};
return (
<Box sx={{ position: 'relative', width: '100%', p: 2 }}>
{/* Menu Button - Now positioned at top left */}
<Button
variant="outlined"
onClick={() => navigate('/')}
sx={{
position: 'absolute',
left: 16,
top: 16,
}}
>
Main Menu
</Button>
<Grid container spacing={2} justifyContent="center">
<Grid item xs={12}>
<Typography variant="h5" align="center" gutterBottom>
{gameType} Round - Round {state.currentGame.rounds.length + 1}
</Typography>
</Grid>
{/* Compact score input section */}
<Grid item xs={12}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
gap: 1,
mb: 2
}}
>
{Array.from({ length: maxArrowsPerRound }).map((_, index) => (
<TextField
key={index}
value={arrowScores[index]}
onChange={(e) => handleScoreChange(index, e.target.value)}
placeholder="0"
size="small"
sx={{
width: '60px',
'& .MuiInputBase-input': {
padding: '8px',
textAlign: 'center'
}
}}
inputProps={{
maxLength: 1,
style: { textAlign: 'center' }
}}
/>
))}
</Box>
</Grid>
{/* Score buttons */}
<Grid item xs={12}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gap: 1,
mb: 2
}}
>
{Array.from({ length: maxScore + 1 }).map((_, i) => (
<Button
key={i}
variant="outlined"
size="small"
sx={{ minWidth: '40px', height: '40px' }}
onClick={() => {
const emptyIndex = arrowScores.findIndex(score => score === '');
if (emptyIndex >= 0 && emptyIndex < maxArrowsPerRound) {
handleScoreChange(emptyIndex, i.toString());
}
}}
>
{i}
</Button>
))}
<Button
variant="outlined"
size="small"
sx={{ minWidth: '40px', height: '40px' }}
onClick={() => {
const emptyIndex = arrowScores.findIndex(score => score === '');
if (emptyIndex >= 0 && emptyIndex < maxArrowsPerRound) {
handleScoreChange(emptyIndex, 'X');
}
}}
>
X
</Button>
</Box>
</Grid>
{/* Control buttons */}
<Grid item xs={12}>
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2 }}>
<Button
variant="contained"
color="primary"
onClick={handleAddRound}
disabled={!arrowScores.slice(0, maxArrowsPerRound).every(score => score !== '')}
>
Add Round
</Button>
<Button
variant="outlined"
onClick={() => setArrowScores(Array(maxArrowsPerRound).fill(''))}
>
Clear
</Button>
</Box>
</Grid>
{/* Scores display */}
<Grid item xs={12}>
<Typography variant="h6" align="center">
Total Score: {state.currentGame.totalScore} |
Bullseyes: {state.currentGame.totalBullseyes}
</Typography>
</Grid>
{/* Round history */}
<Grid item xs={12}>
<Box>
{state.currentGame && state.currentGame.rounds && state.currentGame.rounds.length > 0 ? (
state.currentGame.rounds.map((round, roundIndex) => (
<Typography key={roundIndex} variant="body2" align="center">
Round {roundIndex + 1}: {round.arrows.map(arrow => arrow.isBullseye ? 'X' : arrow.score).join(', ')}
(Total: {round.total || 0}, Bullseyes: {round.bullseyes || 0})
</Typography>
))
) : (
<Typography variant="body2" align="center">
No rounds played yet.
</Typography>
)}
</Box>
</Grid>
</Grid>
</Box>
);
};
export default ScoreTracker;

View File

@@ -1,37 +0,0 @@
import React from 'react';
import { useScore, ACTIONS } from '../context/ScoreContext';
import { useNavigate } from 'react-router-dom';
import { Button, Typography, Box, Grid } from '@mui/material';
const GameSummary = () => {
const { state, dispatch } = useScore();
const navigate = useNavigate();
const handleReturnToMenu = () => {
dispatch({ type: ACTIONS.RESET_GAME });
navigate('/');
};
return (
<Box sx={{ p: 4 }}>
<Typography variant="h4" gutterBottom align="center">
Game Summary
</Typography>
{/* Your existing summary content here */}
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'center' }}>
<Button
variant="contained"
color="primary"
onClick={handleReturnToMenu}
sx={{ minWidth: 200 }}
>
Return to Main Menu
</Button>
</Box>
</Box>
);
};
export default GameSummary;

View File

@@ -1,72 +1,44 @@
// src/context/ScoreContext.js
import React, { createContext, useContext, useReducer, useEffect } from 'react'; import React, { createContext, useContext, useReducer, useEffect } from 'react';
// Define action types
export const ACTIONS = { export const ACTIONS = {
ADD_ARROW: 'ADD_ARROW', START_GAME: 'start_game',
START_NEW_ROUND: 'START_NEW_ROUND', ADD_ROUND: 'add_round',
RESET_GAME: 'RESET_GAME', RESET_GAME: 'reset_game',
LOAD_SAVED_GAME: 'LOAD_SAVED_GAME', SAVE_GAME: 'save_game',
START_NEW_GAME: 'START_NEW_GAME', LOAD_HISTORY: 'load_history'
SAVE_GAME: 'SAVE_GAME',
UPDATE_HANDICAP: 'UPDATE_HANDICAP'
}; };
// Initial state structure
const initialState = { const initialState = {
currentGame: { currentGame: {
id: null, gameType: '',
gameType: null, // '450' or '300' isLeague: false,
category: null, // 'league' or 'practice'
rounds: [], rounds: [],
totalScore: 0, totalScore: 0,
totalBullseyes: 0, totalBullseyes: 0,
dateStarted: null, date: null
dateCompleted: null,
handicap: null,
},
games: {
league: [],
practice: [],
},
statistics: {
handicapHistory: [],
averageScores: {
league: { '450': 0, '300': 0 },
practice: { '450': 0, '300': 0 },
},
}, },
history: []
}; };
// Handicap calculation function
const calculateHandicap = (scores) => {
if (scores.length < 3) return null;
const lastThree = scores.slice(-3);
const average = lastThree.reduce((sum, game) => sum + game.totalScore, 0) / 3;
const maxPossibleScore = scores[0].gameType === '450' ? 450 : 300;
const handicap = Math.round((maxPossibleScore - average) * 0.8);
return Math.max(0, Math.min(100, handicap));
};
// Reducer function
const scoreReducer = (state, action) => { const scoreReducer = (state, action) => {
switch (action.type) { switch (action.type) {
case ACTIONS.ADD_ARROW: { case ACTIONS.START_GAME:
const { roundIndex, score, isBullseye } = action.payload; return {
const updatedRounds = [...state.currentGame.rounds]; ...state,
currentGame: {
if (!updatedRounds[roundIndex]) { ...initialState.currentGame,
updatedRounds[roundIndex] = { arrows: [], total: 0, bullseyes: 0 }; gameType: action.payload.gameType,
isLeague: action.payload.isLeague,
date: new Date().toISOString()
} }
};
updatedRounds[roundIndex] = { case ACTIONS.ADD_ROUND:
...updatedRounds[roundIndex], const updatedRounds = [...state.currentGame.rounds];
arrows: [...updatedRounds[roundIndex].arrows, score], updatedRounds[action.payload.roundIndex] = {
total: updatedRounds[roundIndex].total + score, arrows: action.payload.arrows,
bullseyes: updatedRounds[roundIndex].bullseyes + (isBullseye ? 1 : 0), total: action.payload.arrows.reduce((sum, arrow) => sum + arrow.score, 0),
bullseyes: action.payload.arrows.filter(arrow => arrow.isBullseye).length
}; };
const totalScore = updatedRounds.reduce((sum, round) => sum + round.total, 0); const totalScore = updatedRounds.reduce((sum, round) => sum + round.total, 0);
@@ -78,130 +50,58 @@ const scoreReducer = (state, action) => {
...state.currentGame, ...state.currentGame,
rounds: updatedRounds, rounds: updatedRounds,
totalScore, totalScore,
totalBullseyes, totalBullseyes
},
};
} }
};
case ACTIONS.START_NEW_ROUND: { case ACTIONS.SAVE_GAME:
const gamesHistory = state.currentGame.gameType ? if (!state.currentGame.isLeague) return state; // Only save league games
[...state.games, state.currentGame] :
state.games; const updatedHistory = [
...state.history,
{ ...state.currentGame }
];
// Save to localStorage
localStorage.setItem('archeryHistory', JSON.stringify(updatedHistory));
return { return {
...state, ...state,
games: gamesHistory, history: updatedHistory
currentGame: {
...state.currentGame,
gameType: action.payload.gameType,
rounds: [],
totalScore: 0,
totalBullseyes: 0,
dateStarted: new Date().toISOString(),
},
}; };
}
case ACTIONS.START_NEW_GAME: { case ACTIONS.LOAD_HISTORY:
const { gameType, category } = action.payload;
return { return {
...state, ...state,
currentGame: { history: action.payload
...initialState.currentGame,
id: Date.now(),
gameType,
category,
dateStarted: new Date().toISOString(),
},
}; };
}
case ACTIONS.SAVE_GAME: {
const completedGame = {
...state.currentGame,
dateCompleted: new Date().toISOString(),
};
if (completedGame.category === 'league') {
const relevantGames = [...state.games.league, completedGame]
.filter(game => game.gameType === completedGame.gameType)
.sort((a, b) => new Date(b.dateCompleted) - new Date(a.dateCompleted));
completedGame.handicap = calculateHandicap(relevantGames);
}
const newGames = {
...state.games,
[completedGame.category]: [
...state.games[completedGame.category],
completedGame,
],
};
const updateAverages = (games, type, category) => {
const relevantGames = games.filter(game => game.gameType === type);
return relevantGames.length > 0
? relevantGames.reduce((sum, game) => sum + game.totalScore, 0) / relevantGames.length
: 0;
};
const newStatistics = {
...state.statistics,
averageScores: {
league: {
'450': updateAverages(newGames.league, '450', 'league'),
'300': updateAverages(newGames.league, '300', 'league'),
},
practice: {
'450': updateAverages(newGames.practice, '450', 'practice'),
'300': updateAverages(newGames.practice, '300', 'practice'),
},
},
};
return {
...state,
currentGame: initialState.currentGame,
games: newGames,
statistics: newStatistics,
};
}
case ACTIONS.RESET_GAME: case ACTIONS.RESET_GAME:
return initialState; return {
...state,
case ACTIONS.LOAD_SAVED_GAME: currentGame: initialState.currentGame
return action.payload; };
default: default:
return state; return state;
} }
}; };
// Create context
const ScoreContext = createContext(); const ScoreContext = createContext();
// Context provider component
export const ScoreProvider = ({ children }) => { export const ScoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(scoreReducer, initialState, () => { const [state, dispatch] = useReducer(scoreReducer, initialState);
try {
const localData = localStorage.getItem('archeryScores');
if (localData) {
const parsedData = JSON.parse(localData);
if (parsedData.currentGame && parsedData.games) {
return parsedData;
}
}
} catch (error) {
console.error('Error loading saved game:', error);
}
return initialState;
});
// Save to localStorage after every state change // Load history from localStorage on initial mount
useEffect(() => { useEffect(() => {
localStorage.setItem('archeryScores', JSON.stringify(state)); const savedHistory = localStorage.getItem('archeryHistory');
}, [state]); if (savedHistory) {
dispatch({
type: ACTIONS.LOAD_HISTORY,
payload: JSON.parse(savedHistory)
});
}
}, []);
return ( return (
<ScoreContext.Provider value={{ state, dispatch }}> <ScoreContext.Provider value={{ state, dispatch }}>
@@ -210,7 +110,6 @@ export const ScoreProvider = ({ children }) => {
); );
}; };
// Custom hook for using the score context
export const useScore = () => { export const useScore = () => {
const context = useContext(ScoreContext); const context = useContext(ScoreContext);
if (!context) { if (!context) {
@@ -218,5 +117,3 @@ export const useScore = () => {
} }
return context; return context;
}; };
export default ScoreContext;

View File

@@ -0,0 +1,47 @@
// File: src/context/ThemeContext.js
import React, { createContext, useState, useContext, useEffect, useMemo } from 'react';
import { ThemeProvider as MuiThemeProvider, createTheme } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
const ThemeContext = createContext();
export const useTheme = () => useContext(ThemeContext);
export function ThemeProvider({ children }) {
const [mode, setMode] = useState(() => {
try {
const savedMode = localStorage.getItem('themeMode');
return savedMode || 'light';
} catch (e) {
return 'light';
}
});
useEffect(() => {
localStorage.setItem('themeMode', mode);
}, [mode]);
const theme = useMemo(
() =>
createTheme({
palette: {
mode,
},
}),
[mode]
);
const toggleTheme = () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ mode, toggleTheme }}>
<MuiThemeProvider theme={theme}>
<CssBaseline />
{children}
</MuiThemeProvider>
</ThemeContext.Provider>
);
}