Skip to content

Commit

Permalink
userSettings + theme change working
Browse files Browse the repository at this point in the history
  • Loading branch information
m-usaid99 committed Aug 4, 2024
1 parent 81bc189 commit d287403
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 44 deletions.
13 changes: 6 additions & 7 deletions frontend/src/ThemeContext.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import React, { createContext, useMemo, useState, useContext } from 'react';
// src/ThemeContext.js
import React, { createContext, useMemo, useState, useContext, useEffect } from 'react';
import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
import { lightTheme, darkTheme } from './theme';
import { useSelector } from 'react-redux';

const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
const [mode, setMode] = useState('light');
const mode = useSelector((state) => state.user.theme);
const theme = useMemo(() => (mode === 'light' ? lightTheme : darkTheme), [mode]);

const toggleTheme = () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
};

return (
<ThemeContext.Provider value={{ mode, toggleTheme }}>
<ThemeContext.Provider value={{ mode }}>
<MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>
</ThemeContext.Provider>
);
};

export const useTheme = () => useContext(ThemeContext);

14 changes: 12 additions & 2 deletions frontend/src/api/userService.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const requestPasswordReset = async (email) => {
console.error('Error requesting password reset', error);
throw error;
}
}
};

export const resetPassword = async (token, password) => {
try {
Expand All @@ -37,4 +37,14 @@ export const resetPassword = async (token, password) => {
console.error('Error resetting password:', error);
throw error;
}
}
};

export const updateUserProfile = async (profileData) => {
const response = await apiClient.put('/users/profile', profileData);
return response;
};

export const updateUserSettings = async (settings) => {
const response = await apiClient.put('/users/settings', settings);
return response.data;
};
14 changes: 13 additions & 1 deletion frontend/src/components/NavBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,33 @@ import AccountCircle from "@mui/icons-material/AccountCircle";
import Logout from "@mui/icons-material/Logout";
import { Link } from "react-router-dom";
import SettingsModal from "./SettingsModal"; // Adjust the import path accordingly
import ProfileModal from "./ProfileModal";
import { useDispatch } from "react-redux";
import { logout } from '../features/user/userSlice';

const NavBar = () => {
const [anchorEl, setAnchorEl] = useState(null);
const [settingsOpen, setSettingsOpen] = useState(false);
const [profileOpen, setProfileOpen] = useState(false);
const dispatch = useDispatch();

const handleMenuOpen = (event) => {
setAnchorEl(event.currentTarget);
};

const handleProfileOpen = () => {
setProfileOpen(true);
handleMenuClose();
};

const handleMenuClose = () => {
setAnchorEl(null);
};

const handleProfileClose = () => {
setProfileOpen(false);
};

const handleSettingsOpen = () => {
setSettingsOpen(true);
handleMenuClose();
Expand Down Expand Up @@ -76,7 +87,7 @@ const NavBar = () => {
},
}}
>
<MenuItem component={Link} to="/profile">
<MenuItem onClick={handleProfileOpen}>
Profile
</MenuItem>
<MenuItem onClick={handleSettingsOpen}>
Expand All @@ -95,6 +106,7 @@ const NavBar = () => {
</Toolbar>
</AppBar>
<SettingsModal open={settingsOpen} handleClose={handleSettingsClose} />
<ProfileModal open={profileOpen} handleClose={handleProfileClose} />
</>
);
};
Expand Down
136 changes: 136 additions & 0 deletions frontend/src/components/ProfileModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { useState } from 'react';
import { Modal, Box, Typography, TextField, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@mui/material';
import { useDispatch, useSelector } from 'react-redux';
import { updateUserProfileAsync } from '../features/user/userSlice';

const ProfileModal = ({ open, handleClose }) => {
const dispatch = useDispatch();
const { userInfo } = useSelector((state) => state.user);
const [name, setName] = useState(userInfo.name);
const [email, setEmail] = useState(userInfo.email);
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [confirmationOpen, setConfirmationOpen] = useState(false);
const [message, setMessage] = useState('');

const handleSave = () => {
if (password !== confirmPassword) {
setMessage('Passwords do not match!');
return;
}
setMessage('');
setConfirmationOpen(true);
};

const handleConfirmSave = () => {
const updatedProfile = {
name,
email,
password: password ? password : undefined,
};
dispatch(updateUserProfileAsync(updatedProfile));
setConfirmationOpen(false);
handleClose();
};

const handleCancel = () => {
setConfirmationOpen(false);
};

return (
<>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="profile-modal-title"
aria-describedby="profile-modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
}}
>
<Typography id="profile-modal-title" variant="h6" component="h2">
Update Profile
</Typography>
{message && (
<Typography color="error" sx={{ mt: 1 }}>
{message}
</Typography>
)}
<TextField
label="Name"
fullWidth
margin="normal"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<TextField
label="Email"
fullWidth
margin="normal"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<TextField
label="Password"
fullWidth
margin="normal"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<TextField
label="Confirm Password"
fullWidth
margin="normal"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<Button
variant="contained"
color="primary"
onClick={handleSave}
sx={{ mt: 2 }}
>
Save
</Button>
</Box>
</Modal>

<Dialog
open={confirmationOpen}
onClose={handleCancel}
aria-labelledby="confirm-dialog-title"
aria-describedby="confirm-dialog-description"
>
<DialogTitle id="confirm-dialog-title">Confirm Profile Update</DialogTitle>
<DialogContent>
<DialogContentText id="confirm-dialog-description">
Are you sure you want to update your profile details?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleCancel} color="secondary">
Cancel
</Button>
<Button onClick={handleConfirmSave} color="primary" autoFocus>
Confirm
</Button>
</DialogActions>
</Dialog>
</>
);
};

export default ProfileModal;

30 changes: 24 additions & 6 deletions frontend/src/components/SettingsModal.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
// src/components/SettingsModal.js
import React from 'react';
import { Modal, Box, Typography, Switch, FormControlLabel, TextField, MenuItem } from '@mui/material';
import { useTheme } from '../ThemeContext';
import { Modal, Box, Typography, Switch, FormControlLabel, TextField, MenuItem, Button } from '@mui/material';
import { useDispatch, useSelector } from 'react-redux';
import { toggleTheme, updateUserSettingsAsync } from '../features/user/userSlice';

const SettingsModal = ({ open, handleClose }) => {
const { mode, toggleTheme } = useTheme();
const dispatch = useDispatch();
const mode = useSelector((state) => state.user.theme);

const handleToggleTheme = () => {
dispatch(toggleTheme());
};

const handleSaveSettings = () => {
dispatch(updateUserSettingsAsync({ theme: mode }));
handleClose();
};

return (
<Modal
Expand Down Expand Up @@ -33,11 +44,10 @@ const SettingsModal = ({ open, handleClose }) => {
control={
<Switch
checked={mode === 'dark'}
onChange={toggleTheme}
onChange={handleToggleTheme}
name="themeToggle"
color="primary"
/>
}
/>}
label="Switch To Dark Mode"
sx={{ marginTop: 1 }}
/>
Expand All @@ -64,6 +74,14 @@ const SettingsModal = ({ open, handleClose }) => {
</MenuItem>
</TextField>
</Box>
<Button
variant="contained"
color="primary"
onClick={handleSaveSettings}
sx={{ mt: 3 }}
>
Save Settings
</Button>
</Box>
</Modal>
);
Expand Down
64 changes: 46 additions & 18 deletions frontend/src/components/Sidebar.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,78 @@
// src/components/Sidebar.js
import React from 'react';
import { Drawer, List, ListItem, ListItemIcon, ListItemText, Toolbar } from '@mui/material';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ReceiptIcon from '@mui/icons-material/Receipt';
import MonetizationOnIcon from '@mui/icons-material/MonetizationOn';
import PieChartIcon from '@mui/icons-material/PieChart';
import BarChartIcon from '@mui/icons-material/BarChart';
import { Drawer, List, ListItem, ListItemIcon, ListItemText, Toolbar, Typography } from '@mui/material';
import { Dashboard as DashboardIcon, Receipt as ReceiptIcon, MonetizationOn as MonetizationOnIcon, PieChart as PieChartIcon, BarChart as BarChartIcon } from '@mui/icons-material';
import { Link } from 'react-router-dom';
import { useTheme } from '@mui/material/styles';

const Sidebar = () => {
const drawerWidth = 240;
const theme = useTheme();

return (
<Drawer
variant="permanent"
sx={{
width: drawerWidth,
flexShrink: 0,
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box' },
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box', bgcolor: theme.palette.background.default },
}}
>
<Toolbar />
<List>
<ListItem component={Link} to="/dashboard">
<ListItem component={Link} to="/dashboard" sx={{ py: 1 }}>
<ListItemIcon><DashboardIcon /></ListItemIcon>
<ListItemText primary="Dashboard" />
<ListItemText
primary={
<Typography variant="body1" style={{ color: theme.palette.text.primary }}>
Dashboard
</Typography>
}
/>
</ListItem>
<ListItem component={Link} to="/expenses">
<ListItem component={Link} to="/expenses" sx={{ py: 1 }}>
<ListItemIcon><ReceiptIcon /></ListItemIcon>
<ListItemText primary="Expenses" />
<ListItemText
primary={
<Typography variant="body1" style={{ color: theme.palette.text.primary }}>
Expenses
</Typography>
}
/>
</ListItem>
<ListItem component={Link} to="/income">
<ListItem component={Link} to="/income" sx={{ py: 1 }}>
<ListItemIcon><MonetizationOnIcon /></ListItemIcon>
<ListItemText primary="Income" />
<ListItemText
primary={
<Typography variant="body1" style={{ color: theme.palette.text.primary }}>
Income
</Typography>
}
/>
</ListItem>
<ListItem component={Link} to="/budget">
<ListItem component={Link} to="/budget" sx={{ py: 1 }}>
<ListItemIcon><PieChartIcon /></ListItemIcon>
<ListItemText primary="Budget" />
<ListItemText
primary={
<Typography variant="body1" style={{ color: theme.palette.text.primary }}>
Budget
</Typography>
}
/>
</ListItem>
<ListItem component={Link} to="/reports">
<ListItem component={Link} to="/reports" sx={{ py: 1 }}>
<ListItemIcon><BarChartIcon /></ListItemIcon>
<ListItemText primary="Reports" />
<ListItemText
primary={
<Typography variant="body1" style={{ color: theme.palette.text.primary }}>
Reports
</Typography>
}
/>
</ListItem>
</List>
</Drawer>
);
};

export default Sidebar;

Loading

0 comments on commit d287403

Please sign in to comment.