diff --git a/client-side/public/images/notification.svg b/client-side/public/images/notification.svg
new file mode 100644
index 000000000..79c5feb78
--- /dev/null
+++ b/client-side/public/images/notification.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client-side/public/images/pencil.svg b/client-side/public/images/pencil.svg
new file mode 100644
index 000000000..a63cf6de5
--- /dev/null
+++ b/client-side/public/images/pencil.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/client-side/src/components/signUp/PasswordStrength.jsx b/client-side/src/components/signUp/PasswordStrength.jsx
new file mode 100644
index 000000000..799314b60
--- /dev/null
+++ b/client-side/src/components/signUp/PasswordStrength.jsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { Box, LinearProgress } from '@mui/material';
+import './PasswordStrength.scss';
+import { PASSWORD_STRENGTH } from '../../constants';
+
+const PasswordStrengthMeter = ({ password }) => {
+
+ const colors = {
+ passwordStrength: {
+ empty: '#f00',
+ weak: '#ffa500',
+ medium: '#008000',
+ }
+};
+ const calculatePasswordStrength = (password) => {
+ if (!password) return PASSWORD_STRENGTH.EMPTY;
+ if (password.length < 4) return PASSWORD_STRENGTH.WEAK;
+ if (/[A-Za-z]/.test(password) && /[0-9]/.test(password) && password.length > 4) return PASSWORD_STRENGTH.MEDIUM;
+ return PASSWORD_STRENGTH.WEAK;
+ };
+ const strength = calculatePasswordStrength(password);
+
+ const { color, message } = strength;
+ const strengthColor = colors.passwordStrength[color];
+ const strengthClass = {
+ [PASSWORD_STRENGTH.EMPTY.key]: 'weak',
+ [PASSWORD_STRENGTH.WEAK.key]: 'medium',
+ [PASSWORD_STRENGTH.MEDIUM.key]: 'strong',
+ }[strength.key];
+ return (
+
+
+
+ {message}
+
+
+);
+};
+export default PasswordStrengthMeter;
diff --git a/client-side/src/components/signUp/PasswordStrength.scss b/client-side/src/components/signUp/PasswordStrength.scss
new file mode 100644
index 000000000..d1e20eaff
--- /dev/null
+++ b/client-side/src/components/signUp/PasswordStrength.scss
@@ -0,0 +1,35 @@
+.centered-container {
+ width: 50%;
+ margin-left: 2%;
+ }
+
+ .password-strength-meter {
+ height: 10px;
+ border-radius: 5px;
+ right: 3%;
+ background-color: #D3D3D3;
+
+ &.weak {
+ & .MuiLinearProgress-bar {
+ background-color: red;
+ }
+ }
+
+ &.medium {
+ & .MuiLinearProgress-bar {
+ background-color: orange;
+ }
+ }
+
+ &.strong {
+ & .MuiLinearProgress-bar {
+ background-color: green;
+ }
+ }
+ }
+
+ .password-strength-message {
+ margin-top: 1px;
+ margin-right: 34%;
+ font-size: small;
+ }
\ No newline at end of file
diff --git a/client-side/src/components/signUp/signUp.jsx b/client-side/src/components/signUp/signUp.jsx
new file mode 100644
index 000000000..25328e434
--- /dev/null
+++ b/client-side/src/components/signUp/signUp.jsx
@@ -0,0 +1,166 @@
+import React, { useEffect, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { enqueueSnackbar, useSnackbar } from 'notistack';
+// import ReCAPTCHA from 'react-google-recaptcha';
+import { useFormik } from 'formik';
+import * as Yup from 'yup';
+import { useNavigate } from 'react-router-dom';
+import PasswordStrengthMeter from '../signUp/PasswordStrength';
+import GenericButton from '../../stories/Button/GenericButton';
+import ToastMessage from '../../stories/Toast/ToastMessage.jsx';
+import GenericInput from '../../stories/GenericInput/genericInput';
+import { MessagesSignUp,TOAST_MESSAGES } from '../../constants';
+import { createUser, updateUser } from '../../services/userService';
+import { addUser, updateUserDetails } from '../../redux/user/user.slice';
+import './signUp.scss';
+
+const SignUpSchema = Yup.object().shape({
+ name: Yup.string()
+ .matches(/^[a-zA-Z\u0590-\u05FF\s]+$/, MessagesSignUp.name.matches)
+ .required(MessagesSignUp.name.required),
+ email: Yup.string()
+ .email(MessagesSignUp.email.invalid)
+ .required(MessagesSignUp.email.required),
+ password: Yup.string()
+ .required(MessagesSignUp.password.required)
+ .matches(/[A-Za-z]/, MessagesSignUp.password.matches.letters)
+ .matches(/\d/, MessagesSignUp.password.matches.digits)
+ .min(4, MessagesSignUp.password.min)
+});
+function SignUp({ isEditMode = false, onSave }) {
+ const [password, setPassword] = useState('');
+// const [robotPass, setRobotPass] = useState(null)
+ const url = process.env.REACT_APP_SITEKEY;
+ const dispatch = useDispatch();
+ const navigate=useNavigate();
+const user = useSelector(state => state.user.currentUser|| {});
+const userId =user.id || null;
+const formik = useFormik({
+ initialValues:{ name: user.name || '', email: user.email || '', password: '' },
+ validationSchema: SignUpSchema,
+ onSubmit: (values) => {
+ const user = {
+ name: values.name,
+ email: values.email,
+ password: values.password,
+ };
+ if (isEditMode) {
+ editUser(user);
+ } else {
+ userSignUp(user);
+ }
+ },
+ });
+useEffect(() => {
+ if (isEditMode) {
+ formik.setValues({ name: user.name || '', email: user.email || '', password: '' });
+ }
+}, [isEditMode]);
+const resetFormValues = () => {
+ formik.resetForm({
+ values: {
+ name: user.name || '',
+ email: user.email || '',
+ password: ''
+ }
+ });
+};
+
+ const userSignUp = async (user) => {
+ try {
+ await createUser(user);
+ dispatch(addUser(user));
+ navigate('/home');
+ } catch (error) {
+ console.error("The user is not included in the system");
+ }
+ };
+ const editUser = async (user) => {
+ try {
+ await updateUser(user,userId);
+ dispatch(updateUserDetails(user));
+ enqueueSnackbar();
+ navigate('/home');
+ } catch (error) {
+ if (error.response && error.response.status === 401) {
+ enqueueSnackbar();
+ }else{
+ enqueueSnackbar();
+ }
+ resetFormValues();
+ }
+ };
+ return (
+
+
{isEditMode ? 'Edit User' : 'Sign Up'}
+
+
+ );
+}
+export default SignUp;
+
diff --git a/client-side/src/components/signUp/signUp.scss b/client-side/src/components/signUp/signUp.scss
new file mode 100644
index 000000000..b9467e817
--- /dev/null
+++ b/client-side/src/components/signUp/signUp.scss
@@ -0,0 +1,62 @@
+
+$primary-color: #ccc;
+$border-radius: 1%;
+$direction: rtl;
+.signup-container {
+ max-width: 25%;
+ margin: 0 auto;
+ padding: 1%;
+ border: 1px solid $primary-color;
+ border-radius: $border-radius;
+ box-shadow: 0 0 1% rgba(0, 0, 0, 0.1);
+ text-align: $direction;
+}
+h2 {
+ margin-bottom: 2%;
+ direction: ltr;
+ margin-right: calc(100% - 78%);
+}
+.form-group {
+ margin-bottom: 3%;
+}
+@mixin form-element {
+ display: block;
+ margin-bottom: 2%;
+
+ & + & {
+ margin-top: 1%;
+ }
+}
+label {
+ @include form-element;
+}
+
+input {
+ @include form-element;
+
+ width: 100%;
+ padding: 1%;
+ box-sizing: border-box;
+ border: 1px solid $primary-color;
+ border-radius: $border-radius;
+ direction: $direction;
+ margin-bottom: 16px;
+}
+button {
+ width: 100%;
+ padding: 2%;
+ color: white;
+ border: none;
+ border-radius: $border-radius;
+ cursor: pointer;
+ font-size: 5%;
+}
+.error {
+ color: red;
+ font-size: small;
+ margin-top: 1%;
+ margin-right: calc(100% - 54%);
+}
+.css-eglki6-MuiLinearProgress-root{
+ margin-top: 12px;
+}
\ No newline at end of file
diff --git a/client-side/src/constants.js b/client-side/src/constants.js
index 59c25eb6f..ff0d09f4e 100644
--- a/client-side/src/constants.js
+++ b/client-side/src/constants.js
@@ -13,4 +13,49 @@ export const MESSAGES = {
EMAIL_LABEL: 'Email',
EMAIL_PLACEHOLDER: 'example@example.com',
PASSWORD_LABEL: 'Password',
- };
\ No newline at end of file
+ };
+ export const MessagesSignUp = {
+ name: {
+ required: 'required field',
+ matches: 'The field must contain only letters',
+ },
+ email: {
+ required: 'required field',
+ invalid: 'invalid email',
+ },
+ password: {
+ required: 'required field',
+ matches: {
+ letters: 'field must contain english letter',
+ digits: 'field must contain one digit',
+ },
+ min: 'password must contain at least 4 characters',
+ max: 'password must be up to 20 characters',
+ },
+ };
+ export const PASSWORD_STRENGTH = {
+ EMPTY: {
+ key: 1,
+ message: 'Weak password',
+ color: 'red'
+ },
+ WEAK: {
+ key: 2,
+ message: 'Medium password',
+ color: 'orange'
+ },
+ MEDIUM: {
+ key: 3,
+ message: 'Strong password',
+ color: 'green'
+ }
+ };
+ export const HEADER = {
+ edit_user: 'edit user',
+ manage_notifications:'manage notifications'
+ };
+ export const TOAST_MESSAGES = {
+ USER_UPDATED_SUCCESS: 'User updated successfully!',
+ USER_UPDATED_ERROR: 'Error updating user!',
+ USER_UPDATED_ERROR_UNAUTHORIZED: 'Error updating user, Connect to the site and try again',
+ }
\ No newline at end of file
diff --git a/client-side/src/redux/user/user.slice.js b/client-side/src/redux/user/user.slice.js
index 093863443..e658ee992 100644
--- a/client-side/src/redux/user/user.slice.js
+++ b/client-side/src/redux/user/user.slice.js
@@ -41,6 +41,13 @@ const userSlice = createSlice({
* @param {UserStateType} state
* @param {PayloadAction} action
*/
+ updateUserDetails: (state, action) => {
+ state.currentUser = { ...state.currentUser, ...action.payload };
+ },
+ /**
+ * @param {UserStateType} state
+ * @param {PayloadAction} action
+ */
deleteUser: (state, action) => {
const index = state.users.findIndex(user => user.id === action.payload);
if (index !== -1) {
@@ -50,5 +57,5 @@ const userSlice = createSlice({
}
});
-export const { setUser, addUser, updateUser, deleteUser } = userSlice.actions;
+export const { setUser, addUser, updateUser, deleteUser,updateUserDetails } = userSlice.actions;
export default userSlice.reducer;
diff --git a/client-side/src/router/router.jsx b/client-side/src/router/router.jsx
index 7bfa68907..d993b1010 100644
--- a/client-side/src/router/router.jsx
+++ b/client-side/src/router/router.jsx
@@ -1,8 +1,9 @@
import React from "react";
import {createBrowserRouter } from "react-router-dom";
-import ProfileList from "../components/profileComponent.jsx";
+// import ProfileList from "../components/profileComponent.jsx";
import Layout from "./layout.jsx";
import Login from "../login/Login.jsx";
+import SignUp from "../components/signUp/signUp.jsx";
export const router = createBrowserRouter([
{
path: '',
@@ -16,13 +17,17 @@ export const router = createBrowserRouter([
path: '/home',
element: home
},
- {
- path: '/profiles',
- element:
- },
+ // {
+ // path: '/profiles',
+ // element:
+ // },
{
path: '/login',
element:
+ },
+ {
+ path: '/signup',
+ element:
}
]
},
diff --git a/client-side/src/services/userService.js b/client-side/src/services/userService.js
new file mode 100644
index 000000000..612f950df
--- /dev/null
+++ b/client-side/src/services/userService.js
@@ -0,0 +1,35 @@
+import axios from 'axios';
+import { handlePost, handlePut } from '../axios/middleware';
+const baseURL = process.env.REACT_APP_BASE_URL;
+export const createUser = async (userData) => {
+ try {
+ const response = await handlePost('/users', userData);
+ return response.data;
+ } catch (err) {
+ console.error('Error creating user:', err);
+ throw err;
+ }
+};
+
+function getCookie(name) {
+ const value = `; ${document.cookie}`;
+ const parts = value.split(`; ${name}=`);
+ if (parts.length === 2) return parts.pop().split(';').shift();
+ }
+
+ export const updateUser = async (user, userId) => {
+ try {
+ const token = getCookie('token');
+ const response = await axios.put(`${baseURL}/users/${userId}`,
+ user, {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`
+ }
+ });
+ return response.data;
+ } catch (err) {
+ console.error('Error updating user:', err);
+ throw err;
+ }
+ };
diff --git a/client-side/src/stories/Select/select.scss b/client-side/src/stories/Select/select.scss
index 9bcb03f95..05720974c 100644
--- a/client-side/src/stories/Select/select.scss
+++ b/client-side/src/stories/Select/select.scss
@@ -4,7 +4,7 @@
.genericSelect {
&.primary {
- background-color: $background-class;
+ // background-color: $background-class;
}
&.secondary {
@@ -22,7 +22,7 @@
.MuiOutlinedInput-notchedOutline {
border-width: 2px;
border-color:$secondary-color;
-
+ border-radius: 8px;
legend {
width: auto;
}
diff --git a/client-side/src/stories/Toast/ToastMessage.scss b/client-side/src/stories/Toast/ToastMessage.scss
index 5f87734f7..4b9809d4f 100644
--- a/client-side/src/stories/Toast/ToastMessage.scss
+++ b/client-side/src/stories/Toast/ToastMessage.scss
@@ -1,4 +1,4 @@
-@import './variables.scss';
+@import '../variables.scss';
.MuiAlert-root {
font-family: 'Montserrat', sans-serif;
}
diff --git a/client-side/src/stories/header/header.jsx b/client-side/src/stories/header/header.jsx
index b17391d15..0b281f312 100644
--- a/client-side/src/stories/header/header.jsx
+++ b/client-side/src/stories/header/header.jsx
@@ -4,23 +4,42 @@ import { useSelector } from 'react-redux';
import {AppBar,Box,Toolbar,IconButton,Typography,Menu,AdbIcon,MenuItem,Tooltip,Button,Avatar,Container} from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
import LabTabs from '../tabs/tabs';
+import Select from '../Select/Select.jsx';
import './header.scss';
import { selectAuth } from '../../redux/auth/auth.selector';
+import SignUp from '../../components/signUp/signUp.jsx';
+import { HEADER } from '../../constants.js';
function ResponsiveAppBar() {
const [anchorElNav, setAnchorElNav] = useState(null);
const [anchorElUser, setAnchorElUser] = useState(null);
- const { user } = useSelector(selectAuth);
+ const { user } = useSelector(selectAuth);
+ const [isSelectOpen, setIsSelectOpen] = useState(false);
+ const [isSignupVisible, setIsSignupVisible] = useState(false);
const handleOpenNavMenu = (event) => {
setAnchorElNav(event.currentTarget);
};
const handleOpenUserMenu = (event) => {
setAnchorElUser(event.currentTarget);
+ setIsSelectOpen((prev) => !prev);
};
const handleCloseNavMenu = () => {
setAnchorElNav(null);
};
const handleCloseUserMenu = () => {
setAnchorElUser(null);
+ };
+ const handleSave = () => {
+ setIsSignupVisible(false);
+ };
+
+
+ const selectFunctions = (selectedValue) => {
+ console.log('Selected Value:', selectedValue);
+ if (selectedValue == 1) {
+ setIsSignupVisible(true);
+ } else if (selectedValue == 2) {
+
+ }
};
const getAvatarLetter = () => {
if (user && user.name) {
@@ -85,36 +104,36 @@ function ResponsiveAppBar() {
/>
-
+ {
{getAvatarLetter()}
-
-
+ }
+ {user && user.name && isSelectOpen && (
+
+
+ )}
+ {isSignupVisible && (
+
+ )}
);
}
diff --git a/server-side/controllers/preference.controller.js b/server-side/controllers/preference.controller.js
index 46f71ac85..1744df6f6 100644
--- a/server-side/controllers/preference.controller.js
+++ b/server-side/controllers/preference.controller.js
@@ -64,10 +64,6 @@ export const deletePreference = async (req, res, next) => {
res.json({ message: 'deleted succesfully!!' }).status(204)
} catch (error) {
-<<<<<<< HEAD
- return next({ message: error.message });
-=======
return next({ message: error.message, status: 500 });
->>>>>>> 9b418204928598b6d7eda4b6b9ba01463f7803d9
}
};
diff --git a/server-side/controllers/user.controller.js b/server-side/controllers/user.controller.js
index b5bba2676..fa9bd74d8 100644
--- a/server-side/controllers/user.controller.js
+++ b/server-side/controllers/user.controller.js
@@ -1,6 +1,6 @@
import mongoose from 'mongoose';
import bcrypt from 'bcrypt';
-import Users from '../models/user.model.js';
+import Users, { generateToken } from '../models/user.model.js';
export const getUsers = async (req, res,next) => {
@@ -72,6 +72,10 @@ export const updatedUser = async (req, res,next) => {
if(!mongoose.Types.ObjectId.isValid(id))
return next({message:'id is not valid'})
try {
+ if (req.body.password) {
+ req.body.password = await bcrypt.hash(req.body.password, 10);
+ }
+
if (req.file)
req.body.profileImage = req.file.originalname;
@@ -85,5 +89,32 @@ export const updatedUser = async (req, res,next) => {
next({message:err.message,status:500})
}
};
-
+export const signIn = async (req, res, next) => {
+ try {
+ const { email, password } = req.body;
+ const user = await User.findOne({ email });
+ if (user) {
+ bcrypt.compare( password, user.password, (err, same) => {
+ if (err) {
+ const error = new Error(err.message);
+ error.status = 500;
+ return next(error);
+ }
+ if (same) {
+ user.password = "****";
+ const token=generateToken(user);
+ res.cookie('token', token, { httpOnly: false, secure: true });
+ return res.status(200).send({ user });
+ } else {
+ return res.status(401).send({ message: 'Auth Failed' });
+ }
+ });
+ } else {
+ return res.status(401).send({ message: 'Auth Failed' });
+ }
+ } catch (error) {
+ error.status = 500;
+ return next(error);
+ }
+};
diff --git a/server-side/middleware/auth.js b/server-side/middleware/auth.js
new file mode 100644
index 000000000..c1ca7526a
--- /dev/null
+++ b/server-side/middleware/auth.js
@@ -0,0 +1,14 @@
+import jwt from 'jsonwebtoken';
+
+export const auth = (req, res, next) => {
+ try {
+ const { authorization } = req.headers;
+ const [, token] = authorization.split(' ');
+ const privateKey = process.env.JWT_SECRET || 'JWT_SECRET';
+ const data = jwt.verify(token, privateKey);
+ req.user = data;
+ next();
+ } catch (err) {
+ next({ message: 'Not authorized', status: 401 });
+ }
+};
diff --git a/server-side/models/user.model.js b/server-side/models/user.model.js
index efa9ae126..600c6be7f 100644
--- a/server-side/models/user.model.js
+++ b/server-side/models/user.model.js
@@ -14,3 +14,9 @@ const userSchema = new mongoose.Schema({
});
export default mongoose.model('Users', userSchema);
+export const generateToken = (user) => {
+ const privateKey = process.env.JWT_SECRET || 'JWT_SECRET';
+ const data = { user_id: user._id };
+ const token = jwt.sign(data, privateKey, { expiresIn: '1h' });
+ return token;
+};
\ No newline at end of file
diff --git a/server-side/router/preference.router.js b/server-side/router/preference.router.js
index e0f8baff4..114a4a803 100644
--- a/server-side/router/preference.router.js
+++ b/server-side/router/preference.router.js
@@ -2,15 +2,6 @@ import express from 'express';
import upload from '../middleware/uploadFiles.js';
import {getAllPreference,getPreferenceById,updatePreference,deletePreference,addPreference} from '../controllers/preference.controller.js'
-<<<<<<< HEAD
-const router=express.Router();
-router.get('/preferences',getAllPreference);
-router.get('/preferences/:id',getPreferenceById);
-router.post('/preferences',upload.single('soundVoice'),addPreference);
-router.put('/preferences/:id',upload.single('soundVoice'),updatePreference);
-router.delete('/preferences/:id',deletePreference);
-export default router;
-=======
const preferencesRouter=express.Router();
preferencesRouter.get('/',getAllPreference);
@@ -20,4 +11,3 @@ preferencesRouter.put('/:id',upload.single('soundVoice'),updatePreference);
preferencesRouter.delete('/:id',deletePreference);
export default preferencesRouter;
->>>>>>> 9b418204928598b6d7eda4b6b9ba01463f7803d9
diff --git a/server-side/router/profile.router.js b/server-side/router/profile.router.js
index c8570ba55..971501363 100644
--- a/server-side/router/profile.router.js
+++ b/server-side/router/profile.router.js
@@ -1,5 +1,5 @@
import express from 'express'
-import {getAllProfiles,getProfileById,createProfile,deleteProfile,updateProfile} from '../controllers/profile.controller.js'
+import {getAllProfiles,getProfileById,createProfile,deleteProfile,updateProfile, shareProfile} from '../controllers/profile.controller.js'
const profilesRouter=express.Router();
diff --git a/server-side/router/user.router.js b/server-side/router/user.router.js
index 78a0301a1..319357200 100644
--- a/server-side/router/user.router.js
+++ b/server-side/router/user.router.js
@@ -1,6 +1,7 @@
import express from 'express';
import { getUsers, getUserById, addUser, deleteUser, updatedUser } from '../controllers/user.controller.js';
import upload from '../middleware/uploadFiles.js';
+import { auth as authMiddleware} from '../middleware/auth.js';
const usersRouter = express.Router();
@@ -8,8 +9,7 @@ usersRouter.get('/', getUsers);
usersRouter.get('/:id', getUserById);
usersRouter.post('/',upload.single('profileImage'), addUser);
usersRouter.delete('/:id', deleteUser);
-usersRouter.put('/:id',upload.single('profileImage'), updatedUser);
-
+usersRouter.put('/:id',authMiddleware, updatedUser);
export default usersRouter;