Skip to content

Commit

Permalink
Merge pull request #82 from hatchways/FS-Settings-Page-79
Browse files Browse the repository at this point in the history
Fs settings page 79
  • Loading branch information
amha19 authored Feb 4, 2021
2 parents 42c8eae + 490f725 commit 3c6b913
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 18 deletions.
148 changes: 144 additions & 4 deletions client/src/components/Profile/Settings.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,151 @@
import React from 'react';
import { Grid } from '@material-ui/core';
import { Grid, Typography, FormHelperText, Button } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import { TextField as MikTextField } from 'formik-material-ui';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
import { useSnackbar } from 'notistack';

import * as actions from '../../context/actions';
import { useGlobalContext } from '../../context/studyappContext';
import handleAuthErrors from '../../utils/handleAuthErrors';

export const useStyles = makeStyles(theme => ({
settings: {
margin: theme.spacing(8, 2, 2, 11),
[theme.breakpoints.down('xs')]: {
alignItems: 'center',
margin: theme.spacing(0, 0, 2, 0),
},
},
header: {
marginBottom: theme.spacing(4),
},
form: {
'& div': {
width: '100%',
maxWidth: 350,
},
'& input': {
backgroundColor: theme.palette.common.white,
},
'& button': {
width: '100%',
},
},
helper_text: {
fontSize: '1rem',
margin: theme.spacing(2.5, 0, 0.5, 0),
},
button: {
marginTop: theme.spacing(4),
},
}));

const SignupSchema = Yup.object().shape({
oldPassword: Yup.string().required('Current password is required'),
newPassword: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
confirmPassword: Yup.string().oneOf(
[Yup.ref('newPassword'), null],
'Passwords must match'
),
});

const ProfileSettings = () => {
const classes = useStyles();
const { enqueueSnackbar } = useSnackbar();
const { dispatch } = useGlobalContext();

return (
// Sidebar container
<Grid container>
<h1>This is settings</h1>
<Grid container justify="center">
<Grid item container direction="column" className={classes.settings}>
<Typography variant="h1" className={classes.header}>
Account access
</Typography>
<Typography variant="h5">Change password</Typography>
<Formik
initialValues={{
oldPassword: '',
newPassword: '',
confirmPassword: '',
}}
validationSchema={SignupSchema}
onSubmit={async (values, { setSubmitting, setErrors, resetForm }) => {
console.log(values);
const password = {
oldPassword: values.oldPassword,
newPassword: values.newPassword,
};
actions
.updatePassword(password)(dispatch)
.then(res => {
if (res.status === 200) {
enqueueSnackbar('Updated successfully', {
variant: 'success',
autoHideDuration: '5000',
});
setTimeout(() => {
resetForm();
}, 500);
} else if (res.status === 500) {
enqueueSnackbar('Server Error', {
variant: 'Error',
autoHideDuration: '5000',
});
} else {
handleAuthErrors(res, setErrors);
}
});
setTimeout(() => {
setSubmitting(false);
}, 500);
}}
>
{({ submitForm, isSubmitting, errors }) => (
<Form className={classes.form}>
<FormHelperText className={classes.helper_text}>
Your current password
</FormHelperText>
<Field
component={MikTextField}
name="oldPassword"
type="password"
variant="outlined"
/>
<FormHelperText className={classes.helper_text}>
New password
</FormHelperText>
<Field
component={MikTextField}
name="newPassword"
type="password"
variant="outlined"
/>
<FormHelperText className={classes.helper_text}>
Confirm password
</FormHelperText>
<Field
component={MikTextField}
name="confirmPassword"
type="password"
variant="outlined"
/>
<Grid item>
<Button
color="primary"
className={classes.button}
disabled={isSubmitting}
onClick={submitForm}
>
Update
</Button>
</Grid>
</Form>
)}
</Formik>
</Grid>
</Grid>
);
};
Expand Down
20 changes: 15 additions & 5 deletions client/src/context/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,21 @@ export const fetchProfile = () => async dispatch => {
}
};

export const fetchUserGroups = (userGroups) => async dispatch => {
export const fetchUserGroups = userGroups => async dispatch => {
try {
const res = await axios.get('/user/groups');
const courseGroups = res.data.map(course => course.groups).flat();
dispatch({
type: 'updateUserGroups',
payload: {
groups: [...userGroups],
courseGroups
}
})
courseGroups,
},
});
} catch (err) {
console.log(err.message);
}
}
};

export const updateProfile = userInfo => async dispatch => {
try {
Expand All @@ -101,3 +101,13 @@ export const updateProfile = userInfo => async dispatch => {
return err;
}
};

export const updatePassword = password => async dispatch => {
try {
const res = await axios.put('/auth/changepassword', password);
return res;
} catch (err) {
console.log(err.message);
return err.response;
}
};
7 changes: 4 additions & 3 deletions client/src/utils/handleAuthErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ const handleAuthErrors = (err, setErrors) => {
});
setErrors(errors);
} else if (err.status === 401) {
setErrors({password: 'Invalid password'});
setErrors({ password: 'Invalid password' });
setErrors({ oldPassword: 'Invalid password' });
} else if (err.status === 404) {
setErrors({email: 'User with email not found'});
setErrors({ email: 'User with email not found' });
} else if (err.status === 409) {
setErrors({email: 'Email already in use'});
setErrors({ email: 'Email already in use' });
}
}
};
Expand Down
40 changes: 40 additions & 0 deletions server/controllers/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const { GeneralError, NotFound, Unauthorized } = require('../utils/errors');
const bcrypt = require('bcrypt');
const { validationResult } = require('express-validator');
const User = require('../models/user');

exports.passwordChange = async (req, res, next) => {
const errors = validationResult(req);

if (!errors.isEmpty())
return res.status(400).json({
error: errors.array(),
});

const { oldPassword, newPassword, userId } = req.body;

try {
const user = await User.findById(userId);
if (!user) throw new NotFound('No user found');

const comparePassword = await bcrypt.compare(
oldPassword,
user.password
);
if (!comparePassword)
throw new Unauthorized('Unauthorized to change password');

const newHashedPw = await bcrypt.hash(newPassword, 10);
if (!newHashedPw) throw new GeneralError('Failed to hash password');

user.password = newHashedPw;

const response = await user.save();
if (!response) throw new GeneralError('Failed saving user');

res.status(200).json({ message: 'Password updated successfully' });
} catch (err) {
console.log(err.message);
next(err);
}
};
36 changes: 30 additions & 6 deletions server/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ const jwt = require('jsonwebtoken');
const router = express.Router();
const validateBody = require('../middleware/validateBody');
const validateEntryReq = validateBody.entry;
const { Conflict, GeneralError, NotFound, Unauthorized } = require('../utils/errors');
const auth = require('../middleware/verifyAuth');
const {
Conflict,
GeneralError,
NotFound,
Unauthorized,
} = require('../utils/errors');
const { check } = require('express-validator');
const Profile = require('../models/profile');
const User = require('../models/user');
const userController = require('../controllers/user');

router.post('/login', validateEntryReq, async function (req, res, next) {
const { email, password } = req.body;
Expand All @@ -16,9 +24,11 @@ router.post('/login', validateEntryReq, async function (req, res, next) {
});
if (!user) throw new NotFound('No user found');

const match = await bcrypt.compare(password, user.password).catch(() => {
throw new GeneralError('Error decrypting password');
});
const match = await bcrypt
.compare(password, user.password)
.catch(() => {
throw new GeneralError('Error decrypting password');
});

if (!match) throw new Unauthorized('Invalid credentials');

Expand All @@ -44,7 +54,7 @@ router.post('/register', validateEntryReq, async function (req, res, next) {
});
userInfo.password = hashedPw;
const user = new User(userInfo);
const userDoc = await user.save(user).catch((err) => {
const userDoc = await user.save(user).catch(err => {
console.log(err.Error);
if (!err.errors || (err.errors.email && err.errors.email.reason)) {
throw new GeneralError('Error connecting to database.');
Expand All @@ -56,7 +66,9 @@ router.post('/register', validateEntryReq, async function (req, res, next) {
expiresIn: '180d',
});
await new Profile({ user: userDoc.id }).save().catch(() => {
throw new GeneralError('Error creating user profile on registration.');
throw new GeneralError(
'Error creating user profile on registration.'
);
});
res.cookie('token', token, { httpOnly: true });
res.sendStatus(201);
Expand All @@ -70,4 +82,16 @@ router.delete('/logout', function (req, res) {
res.sendStatus(204);
});

// PUT /auth/changepasword
// Changes user password
router.put(
'/changepassword',
auth,
[
check('oldPassword', 'Old password is required').notEmpty(),
check('newPassword', 'New password is required').notEmpty(),
],
userController.passwordChange
);

module.exports = router;

0 comments on commit 3c6b913

Please sign in to comment.