updated user functionality

This commit is contained in:
Juthatip McDevitt 2024-03-11 20:51:30 -05:00
parent 01adc806d7
commit 0fa054e096
9 changed files with 128 additions and 22 deletions

View file

@ -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 (
<div className='max-w-lg p-3 mx-auto'>
<h1 className="text-2xl uppercase text-center text-blue-900 font-serif my-10 tracking-wide">Profile</h1>
<form className='flex flex-col gap-4'>
<form onSubmit={handleSubmit} className='flex flex-col gap-4'>
<input onChange={(e) => setFile(e.target.files[0])} type="file" ref={fileRef} hidden accept='image/*'/>
<img onClick={() => 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'/>
<img onClick={() => 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'/>
<p className='text-sm self-center'>
{
fileUploadError ? (<span className='text-red-700'>Error image upload (image must be less than 2 mb) </span>) :
@ -54,15 +83,17 @@ export const Profile = () => {
filePercentage === 100 ? (<span className='text-green-900'>Image successfully uploaded</span>) : ("")
}
</p>
<input type="text" placeholder='Username' id='username' className='border p-2 rounded-md text-sm'/>
<input type="email" placeholder='Email' id='email' className='border p-2 rounded-md text-sm'/>
<input type="password" placeholder='Password' id='password' className='border p-2 rounded-md text-sm'/>
<button className='bg-blue-950 text-white text-sm p-2 rounded-md uppercase hover:opacity-90 disabled:opacity-80'>Update</button>
<input type="text" placeholder='Username' id='username' defaultValue={currentUser.username} onChange={handleChange} className='border p-2 rounded-md text-sm'/>
<input type="email" placeholder='Email' id='email' defaultValue={currentUser.email} onChange={handleChange} className='border p-2 rounded-md text-sm'/>
<input type="password" placeholder='Password' id='password' onChange={handleChange} className='border p-2 rounded-md text-sm'/>
<button disabled={loading} className='bg-blue-950 text-white text-sm p-2 rounded-md uppercase hover:opacity-90 disabled:opacity-80'>{loading ? 'Loading...' : 'Update'}</button>
</form>
<div className='flex justify-between mt-4'>
<span className='text-red-700 cursor-pointer'>Delete Account</span>
<span className='text-red-700 cursor-pointer'>Log Out</span>
</div>
<p className='text-red-700 mt-4'>{error ? error : ''}</p>
<p className='text-green-900 mt-4'>{updateSuccess ? 'User profile is updated successfully' : ''}</p>
</div>
)
}

View file

@ -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;

View file

@ -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",

View file

@ -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",

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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,
})
})
})

View file

@ -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

View file

@ -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();
})
}