From 88da73b49b05ed12bec8e60dd08e7b611e719ea4 Mon Sep 17 00:00:00 2001
From: DHRUMIL PATEL <123137675+dhrumilp12@users.noreply.github.com>
Date: Sat, 15 Jun 2024 02:05:23 -0400
Subject: [PATCH] Allow editting required fields #12 - Done
A user's profile must contain required registration fields:
8.a Name
8.b Email Address
8.c Password
---
client/src/Components/passwordUpdateTab.jsx | 111 ++++++++++++++++++++
client/src/Components/userContext.jsx | 30 +++++-
client/src/Components/userProfile.jsx | 55 +++++++++-
server/agents/mental_health_agent.py | 6 ++
server/models/user.py | 18 ++++
server/routes/user.py | 34 ++++++
6 files changed, 249 insertions(+), 5 deletions(-)
create mode 100644 client/src/Components/passwordUpdateTab.jsx
diff --git a/client/src/Components/passwordUpdateTab.jsx b/client/src/Components/passwordUpdateTab.jsx
new file mode 100644
index 00000000..2645cfc8
--- /dev/null
+++ b/client/src/Components/passwordUpdateTab.jsx
@@ -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 (
+
+
+
+
+ Update Password
+
+
+ setSnackbarOpen(false)}>
+ setSnackbarOpen(false)} severity={snackbarType} sx={{ width: '100%' }}>
+ {snackbarMessage}
+
+
+
+
+
+ );
+};
+
+export default PasswordUpdateTab;
diff --git a/client/src/Components/userContext.jsx b/client/src/Components/userContext.jsx
index b8d0e3d1..f7f9bbc4 100644
--- a/client/src/Components/userContext.jsx
+++ b/client/src/Components/userContext.jsx
@@ -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');
@@ -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 (
-
+
{children}
);
diff --git a/client/src/Components/userProfile.jsx b/client/src/Components/userProfile.jsx
index f94fac39..25504a79 100644
--- a/client/src/Components/userProfile.jsx
+++ b/client/src/Components/userProfile.jsx
@@ -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';
@@ -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: {
@@ -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
}));
@@ -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');
@@ -136,6 +174,12 @@ function UserProfile() {
+
+
+
+
+
+ {tabValue === 0 && (
{user.username}
Update Profile
-
+ )}
+ {tabValue === 1 && (
+
+ )}
{message}
diff --git a/server/agents/mental_health_agent.py b/server/agents/mental_health_agent.py
index 3071d532..b36a1128 100644
--- a/server/agents/mental_health_agent.py
+++ b/server/agents/mental_health_agent.py
@@ -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(
diff --git a/server/models/user.py b/server/models/user.py
index 4bcd0276..154be002 100644
--- a/server/models/user.py
+++ b/server/models/user.py
@@ -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):
@@ -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)
diff --git a/server/routes/user.py b/server/routes/user.py
index 92d8a416..c59c1a5b 100644
--- a/server/routes/user.py
+++ b/server/routes/user.py
@@ -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/')
+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():