diff --git a/real_estate/client/src/pages/Profile.jsx b/real_estate/client/src/pages/Profile.jsx index b768f2d..d46d861 100644 --- a/real_estate/client/src/pages/Profile.jsx +++ b/real_estate/client/src/pages/Profile.jsx @@ -2,15 +2,18 @@ import {useSelector} from'react-redux' import { useEffect, useRef, useState } from 'react' import {getDownloadURL, getStorage, ref, uploadBytesResumable} from 'firebase/storage' import { app } from '../firebase'; +import {updateUserStart, updateUserSuccess, updateUserFailure} from '../redux/user/userSlice.js' +import { useDispatch } from 'react-redux'; export const Profile = () => { const fileRef = useRef(null); - const {currentUser} = useSelector((state) => state.user); + const {currentUser, loading, error} = useSelector((state) => state.user); const [file, setFile] = useState(undefined); const [filePercentage, setFilePercentage] = useState(0); const [fileUploadError, setFileUploadError] = useState(false) const [formData, setFormData] = useState({}) - + const [updateSuccess, setUpdateSuccess] = useState(false); + const dispatch = useDispatch(); useEffect (() => { @@ -39,14 +42,40 @@ export const Profile = () => { } ) }; + const handleChange = (e) => { + setFormData({...formData, [e.target.id]: e.target.value}) + } + const handleSubmit = async (e) => { + e.preventDefault(); + try { + dispatch(updateUserStart()); + const res = await fetch(`/server/user/update/${currentUser._id}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData) + }); + const data = await res.json(); + if(data.success === false){ + dispatch(updateUserFailure(data.message)); + return; + } + dispatch(updateUserSuccess(data)); + setUpdateSuccess(true); + + } catch (error) { + dispatch(updateUserFailure(error.message)); + } + }; return (

Profile

-
+ setFile(e.target.files[0])} type="file" ref={fileRef} hidden accept='image/*'/> - fileRef.current.click()} src={formData.avatar || currentUser.avatar} alt="" className='rounded-full h-24 w-24 object-cover self-center mt-2 cursor-pointer bg-blue-100'/> + fileRef.current.click()} src={formData?.avatar || currentUser.avatar} alt="" className='rounded-full h-24 w-24 object-cover self-center mt-2 cursor-pointer bg-blue-100'/>

{ fileUploadError ? (Error image upload (image must be less than 2 mb) ) : @@ -54,15 +83,17 @@ export const Profile = () => { filePercentage === 100 ? (Image successfully uploaded) : ("") }

- - - - + + + +
Delete Account Log Out
+

{error ? error : ''}

+

{updateSuccess ? 'User profile is updated successfully' : ''}

) } diff --git a/real_estate/client/src/redux/user/userSlice.js b/real_estate/client/src/redux/user/userSlice.js index fa8823b..6589e21 100644 --- a/real_estate/client/src/redux/user/userSlice.js +++ b/real_estate/client/src/redux/user/userSlice.js @@ -13,17 +13,29 @@ const userSlice = createSlice({ loginStart: (state) => { state.loading = true; }, - loginSuccess: (state, action) =>{ + loginSuccess: (state, action) => { state.currentUser = action.payload; state.loading = false; state.error = null; }, - loginFailure: (state, action) =>{ + loginFailure: (state, action) => { state.error = action.payload; state.loading = false; - } + }, + updateUserStart: (state) => { + state.loading = true; + }, + updateUserSuccess: (state, action) => { + state.currentUser = action.payload; + state.loading = false; + state.error = null; + }, + updateUserFailure: (state, action) => { + state.error = action.payload; + state.loading = false; + }, } }); -export const {loginStart, loginSuccess, loginFailure} = userSlice.actions; +export const {loginStart, loginSuccess, loginFailure, updateUserStart, updateUserSuccess, updateUserFailure} = userSlice.actions; export default userSlice.reducer; \ No newline at end of file diff --git a/real_estate/package-lock.json b/real_estate/package-lock.json index 5f6d069..c9d4ac9 100644 --- a/real_estate/package-lock.json +++ b/real_estate/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.6", "dotenv": "^16.4.5", "express": "^4.18.3", "jsonwebtoken": "^9.0.2", @@ -227,6 +228,26 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", diff --git a/real_estate/package.json b/real_estate/package.json index cf89dae..e40606a 100644 --- a/real_estate/package.json +++ b/real_estate/package.json @@ -13,6 +13,7 @@ "license": "ISC", "dependencies": { "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.6", "dotenv": "^16.4.5", "express": "^4.18.3", "jsonwebtoken": "^9.0.2", diff --git a/real_estate/server/controllers/auth.controller.js b/real_estate/server/controllers/auth.controller.js index e531635..3a23f71 100644 --- a/real_estate/server/controllers/auth.controller.js +++ b/real_estate/server/controllers/auth.controller.js @@ -2,7 +2,6 @@ import User from "../models/user.model.js"; import bcryptjs from 'bcryptjs' import { errorHandler } from "../utils/error.js"; import jwt from 'jsonwebtoken' -import { userInfo } from "os"; export const signup = async(req, res, next) => { const {username, email, password} = req.body @@ -46,7 +45,7 @@ export const google = async(req, res, next) => { const generatedPassword = Math.random().toString(36).slice(-8) + Math.random().toString(36).slice(-8); const hashedPassword = bcryptjs.hashSync(generatedPassword, 10); const newUser = new User({ - username: req.body.name.split(" ").join("").toLowerCase()+Math.random().toString(36).slice(-4), + username: req.body.name.split(' ').join('').toLowerCase()+Math.random().toString(36).slice(-4), email: req.body.email, password: hashedPassword, avatar: req.body.photo, @@ -55,7 +54,6 @@ export const google = async(req, res, next) => { const token = jwt.sign({id: newUser._id}, process.env.JWT_SECRET); const {password: pass, ...rest} = newUser._doc; res.cookie('access_token', token, {httpOnly: true}).status(200).json(rest); - } } catch (error) { next(error) diff --git a/real_estate/server/controllers/user.controller.js b/real_estate/server/controllers/user.controller.js index 7844dc8..5c1ee41 100644 --- a/real_estate/server/controllers/user.controller.js +++ b/real_estate/server/controllers/user.controller.js @@ -1,5 +1,31 @@ +import bcryptjs from 'bcryptjs'; +import User from '../models/user.model.js'; +import { errorHandler } from '../utils/error.js'; + export const test = (req, res) => { res.json({ message: 'API route is working', }) +}; + +export const updateUser = async (req, res, next) =>{ + if(req.user.id !== req.params.id) return next(errorHandler(401, 'You can only update your account')) + try { + if(req.body.password){ + req.body.password = bcryptjs.hashSync(req.body.password, 10) + } + const updatedUser = await User.findByIdAndUpdate(req.params.id, { + $set:{ + username: req.body.username, + email: req.body.email, + password: req.body.password, + avatar: req.body.avatar, + } + }, {new: true}) + const {password, ...rest} = updatedUser._doc + res.status(200).json(rest); + + } catch (error) { + next(error) + } } \ No newline at end of file diff --git a/real_estate/server/index.js b/real_estate/server/index.js index 5b5c509..bb6d9b6 100644 --- a/real_estate/server/index.js +++ b/real_estate/server/index.js @@ -1,8 +1,9 @@ import express from 'express'; -import mongoose from 'mongoose' -import dotenv from 'dotenv' -import userRouter from './routes/user.route.js' -import authRouter from './routes/auth.route.js' +import mongoose from 'mongoose'; +import dotenv from 'dotenv'; +import userRouter from './routes/user.route.js'; +import authRouter from './routes/auth.route.js'; +import cookieParser from 'cookie-parser'; dotenv.config(); @@ -18,6 +19,7 @@ mongoose.connect(process.env.MONGODB).then(() => { const app = express(); app.use(express.json()); +app.use(cookieParser()); app.listen(3000, () =>{ console.log('Server is running on port 3000') @@ -31,9 +33,9 @@ app.use('/server/auth', authRouter) app.use((err, req, res, next) => { const statusCode = err.statusCode || 500 const message = err.message || "Internal server error" - return res.status(statusCode),json({ + return res.status(statusCode).json({ success: false, statusCode, message, }) -}) \ No newline at end of file +}) diff --git a/real_estate/server/routes/user.route.js b/real_estate/server/routes/user.route.js index 3077860..d75ebd1 100644 --- a/real_estate/server/routes/user.route.js +++ b/real_estate/server/routes/user.route.js @@ -1,8 +1,10 @@ import express from 'express' -import { test } from '../controllers/user.controller.js'; +import { test, updateUser } from '../controllers/user.controller.js'; +import { verifyToken } from '../utils/verifyUser.js'; const router = express.Router(); router.get('/test', test) +router.post('/update/:id', verifyToken, updateUser) export default router \ No newline at end of file diff --git a/real_estate/server/utils/verifyUser.js b/real_estate/server/utils/verifyUser.js new file mode 100644 index 0000000..5441af1 --- /dev/null +++ b/real_estate/server/utils/verifyUser.js @@ -0,0 +1,13 @@ +import jwt from 'jsonwebtoken'; +import { errorHandler } from './error.js'; + +export const verifyToken = (req, res, next) => { + const token = req.cookies.access_token; + if(!token) return next(errorHandler(401, 'Unauthorized')); + + jwt.verify(token, process.env.JWT_SECRET, (err, user) =>{ + if(err) return next(errorHandler(403, 'Forbidden')); + req.user = user; + next(); + }) +} \ No newline at end of file