Skip to content

Commit

Permalink
Allow editting required fields #12 - Done
Browse files Browse the repository at this point in the history
A user's profile must contain required registration fields:
8.a Name
8.b Email Address
8.c Password
  • Loading branch information
dhrumilp12 committed Jun 15, 2024
1 parent d328f90 commit 88da73b
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 5 deletions.
111 changes: 111 additions & 0 deletions client/src/Components/passwordUpdateTab.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React, { useContext, useState } from 'react';
import { UserContext } from './userContext'; // Adjust the import path as necessary
import { useParams } from 'react-router-dom';
import { TextField, Button, Box, Typography, Container, Snackbar, Alert } from '@mui/material';
import { createTheme, ThemeProvider} from '@mui/material/styles';
import VpnKeyIcon from '@mui/icons-material/VpnKey';
import LockIcon from '@mui/icons-material/Lock';

const theme = createTheme({
palette: {
primary: {
main: '#3F51B5',
},
secondary: {
main: '#F6AE2D',
},
},
});


const PasswordUpdateTab = () => {
const { changePassword } = useContext(UserContext);
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState('');
const [snackbarType, setSnackbarType] = useState('success'); // 'success' or 'error'

const { userId } = useParams();
const handleSubmit = async (e) => {
e.preventDefault();
const result = await changePassword(userId, currentPassword, newPassword);
setSnackbarMessage(result.message);
setSnackbarType(result.success ? 'success' : 'error');
setSnackbarOpen(true);
};


return (
<ThemeProvider theme={theme}>
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography component="h1" variant="h5">
Update Password
</Typography>
<form onSubmit={handleSubmit} style={{ width: '100%', marginTop: theme.spacing(1) }}>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="current-password"
label="Current Password"
name="currentPassword"
autoComplete="current-password"
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
InputProps={{
startAdornment: (
<LockIcon color="primary" style={{ marginRight: '10px' }} />
),
}}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="new-password"
label="New Password"
name="newPassword"
autoComplete="new-password"
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
InputProps={{
startAdornment: (
<VpnKeyIcon color="secondary" style={{ marginRight: '10px' }} />
),
}}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
sx={{ mt: 3, mb: 2 }}
>
Update Password
</Button>
</form>
<Snackbar open={snackbarOpen} autoHideDuration={6000} onClose={() => setSnackbarOpen(false)}>
<Alert onClose={() => setSnackbarOpen(false)} severity={snackbarType} sx={{ width: '100%' }}>
{snackbarMessage}
</Alert>
</Snackbar>
</Box>
</Container>
</ThemeProvider>
);
};

export default PasswordUpdateTab;
30 changes: 29 additions & 1 deletion client/src/Components/userContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const UserProvider = ({ children }) => {
const [voiceEnabled, setVoiceEnabled] = useState(false);
const [user, setUser] = useState(null);
const navigate = useNavigate();

const logout = useCallback(async () => {
try {
const token = localStorage.getItem('token');
Expand All @@ -32,8 +33,35 @@ export const UserProvider = ({ children }) => {
}
}, [navigate]);

const changePassword = async (userId, currentPassword, newPassword) => {
try {
const token = localStorage.getItem('token');
const response = await axios.patch(`/api/user/change_password/${userId}`, {
current_password: currentPassword,
new_password: newPassword
}, {
headers: {
Authorization: `Bearer ${token}`, // Make sure the token is being managed in your context or retrieved from somewhere secure
}
});

if (response.status === 200) {
return { success: true, message: 'Password updated successfully!' };
} else {
return { success: false, message: response.data.message || 'Update failed!' };
}
} catch (error) {
if(error.response.status === 403) {
return { success: false, message: error.response.data.message || 'Incorrect current password' };
}
else {
return { success: false, message: error.response?.data?.message || 'Network error' };
}
}
};

return (
<UserContext.Provider value={{ user, setUser, logout,voiceEnabled, setVoiceEnabled }}>
<UserContext.Provider value={{ user, setUser, logout,voiceEnabled, setVoiceEnabled, changePassword }}>
{children}
</UserContext.Provider>
);
Expand Down
55 changes: 51 additions & 4 deletions client/src/Components/userProfile.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import PasswordUpdateTab from './passwordUpdateTab';
import axios from 'axios';

import {
TextField, Button, Container, Typography, Paper, CssBaseline, Snackbar, Alert, FormControl, InputLabel, Select, MenuItem,IconButton
TextField, Button, Container, Typography, Paper, CssBaseline, Snackbar, Alert, FormControl, InputLabel, Select, MenuItem,IconButton,Tabs,Tab,Box
} from '@mui/material';
import { createTheme, ThemeProvider, styled } from '@mui/material/styles';
import EmailIcon from '@mui/icons-material/Email';
Expand All @@ -13,6 +15,37 @@ import HomeIcon from '@mui/icons-material/Home'; // Icon for place of residence
import WorkIcon from '@mui/icons-material/Work';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import UpdateIcon from '@mui/icons-material/Update';

const CustomTabs = styled(Tabs)({
background: '#fff', // Set the background color you prefer
borderRadius: '8px', // Optional: rounded corners
boxShadow: '0 2px 4px rgba(0,0,0,0.1)', // Optional: adds a subtle shadow for depth
margin: '20px 0', // Adds margin around the tabs for spacing
maxWidth: '100%', // Ensures it doesn't overflow its container
overflow: 'hidden', // Prevents any internal content from overflowing
});

const CustomTab = styled(Tab)({
fontSize: '1rem', // Sets the font size to 16px
fontWeight: 'bold', // Makes the font weight bold
color: '#3F51B5', // Uses the primary color defined in the theme
marginRight: '4px', // Adds space between tabs
marginLeft: '4px', // Adds space between tabs
flex: 1, // Each tab flexes to fill available space
maxWidth: 'none', // Allows the tab to grow as needed
'&.Mui-selected': { // Styles for the selected tab
color: '#F6AE2D', // Changes text color when selected
background: '#e0e0e0', // Light grey background on selection
},
'&:hover': { // Styles for hover state
background: '#f4f4f4', // Lighter grey background on hover
transition: 'background-color 0.3s', // Smooth transition for background color
},
'@media (max-width: 720px)': { // Responsive adjustment for smaller screens
padding: '6px 12px', // Reduces padding on smaller screens
fontSize: '0.8rem', // Reduces font size to fit on smaller devices
}});

const theme = createTheme({
palette: {
primary: {
Expand Down Expand Up @@ -57,12 +90,12 @@ const theme = createTheme({
});

const StyledForm = styled(Paper)(({ theme }) => ({
marginTop: theme.spacing(4),
marginTop: theme.spacing(2),
padding: theme.spacing(4),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: theme.spacing(3),
gap: theme.spacing(2),
boxShadow: theme.shadows[3], // Subtle shadow for depth
}));

Expand All @@ -77,6 +110,11 @@ function UserProfile() {
placeOfResidence: '',
fieldOfWork: ''
});
const [tabValue, setTabValue] = useState(0); // To control the active tab

const handleTabChange = (event, newValue) => {
setTabValue(newValue);
};
const [message, setMessage] = useState('');
const [open, setOpen] = useState(false);
const [severity, setSeverity] = useState('info');
Expand Down Expand Up @@ -136,6 +174,12 @@ function UserProfile() {
<ThemeProvider theme={theme}>
<CssBaseline />
<Container component="main" maxWidth="md">
<CustomTabs value={tabValue} onChange={handleTabChange} centered>
<CustomTab label="Profile" />
<CustomTab label="Update Password" />
</CustomTabs>

{tabValue === 0 && (
<StyledForm component="form" onSubmit={handleSubmit}>
<Typography variant="h5" style={{ fontWeight: 700 }}><AccountCircleIcon style={{ marginRight: '10px' }} /> {user.username}</Typography>
<TextField
Expand Down Expand Up @@ -223,7 +267,10 @@ function UserProfile() {
<Button type="submit" color="primary" variant="contained">
<UpdateIcon style={{ marginRight: '10px' }} />Update Profile
</Button>
</StyledForm>
</StyledForm>)}
{tabValue === 1 && (
<PasswordUpdateTab userId={userId} />
)}
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity={severity} sx={{ width: '100%' }}>
{message}
Expand Down
6 changes: 6 additions & 0 deletions server/agents/mental_health_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,13 @@ def get_initial_greeting(self, user_id):
except StopIteration:
last_turn = {}

# Check if 'SessionId' is in last_turn and extract old_chat_id
if 'SessionId' in last_turn:
old_chat_id = int(last_turn["SessionId"].split('-')[-1])
else:
# Initialize old_chat_id if SessionId is not found
old_chat_id = 0

new_chat_id = old_chat_id + 1

response = self.run(
Expand Down
18 changes: 18 additions & 0 deletions server/models/user.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pydantic import BaseModel, EmailStr, Field, field_validator, validator
from services.azure_mongodb import MongoDBClient
from bson import ObjectId

import re
class User(BaseModel):
Expand All @@ -24,6 +25,23 @@ def find_by_username(cls, username):
db_client = MongoDBClient.get_client()
db = db_client[MongoDBClient.get_db_name()]
user_data = db.users.find_one({"username": username}) # 'users' is the collection name
if user_data:
user_data['id'] = str(user_data['_id'])
return cls(**user_data)
return None

@classmethod
def update_password(cls, username, new_hashed_password):
db_client = MongoDBClient.get_client()
db = db_client[MongoDBClient.get_db_name()]
result = db.users.update_one({"username": username}, {"$set": {"password": new_hashed_password}})
return result.modified_count == 1

@classmethod
def find_by_id(cls, user_id):
db_client = MongoDBClient.get_client()
db = db_client[MongoDBClient.get_db_name()]
user_data = db.users.find_one({"_id": ObjectId(user_id)})
if user_data:
user_data['id'] = str(user_data['_id'])
return cls(**user_data)
Expand Down
34 changes: 34 additions & 0 deletions server/routes/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,40 @@ def update_profile_fields(user_id):
return jsonify({"message": "User has been updated successfully."}), 200


@user_routes.patch('/user/change_password/<user_id>')
def change_password(user_id):
try:
# Authenticate user
user_data = request.get_json()
current_password = user_data['current_password']
new_password = user_data['new_password']

user = UserModel.find_by_id(user_id)
if not user:
logging.error("User not found")
return jsonify({"error": "User not found"}), 404

# Verify current password
if not check_password_hash(user.password, current_password):
logging.error("Incorrect current password")
return jsonify({"error": "Incorrect current password"}), 403

# Update to new password
new_password_hash = generate_password_hash(new_password)
if user.update_password(user.username, new_password_hash):
logging.info("Password updated successfully")
return jsonify({"message": "Password successfully updated"}), 200
else:
logging.error("Password update failed")
return jsonify({"error": "Password update failed"}), 500
except KeyError as e:
logging.error(f"Missing data: {str(e)}")
return jsonify({"error": "Missing data"}), 400
except Exception as e:
logging.error(f"Error changing password: {str(e)}")
return jsonify({"error": str(e)}), 500


@user_routes.post('/user/log_mood')
@jwt_required()
def log_mood():
Expand Down

0 comments on commit 88da73b

Please sign in to comment.