-
아마존 E-commerce 클론 -20) UserProfile screen 만들고, Admin 미들웨어 만들기NODE.JS 2021. 5. 28. 16:43
우선 유저디테일을 돌려주는 API를 작성하자.
userRouter.get('/:id', expressAsyncHandler(async(req,res)=>{ const user = await User.findById(req.params.id); if(user){ res.send(user); }else{ res.status(404).send({message:'User Not Found'}); } }))
ProfileScreen.js를 만들어준다.
useSelector를 이용해서 리덕스에 저장된 유저 정보를 가져오고(아이디)
usedispatch를 이용해서 리덕스에 정보를 저장한다.
import React, { useEffect } from 'react' import {useDispatch} from 'react-redux' function ProfileScreen() { const userSignin = useSelector(state => state.userSignin) const {userInfo} = userSignin const dispatch = useDispatch() useEffect(()=>{ dispatch(detailsUser(userInfo._id)); },[dispatch, userInfo._id]) return ( <div> </div> ) } export default ProfileScreen
userConstants를 만들어준다.
export const USER_DETAILS_REQUEST = 'USER_DETAILS_REQUEST' export const USER_DETAILS_SUCCESS = 'USER_DETAILS_SUCCESS' export const USER_DETAILS_FAIL = 'USER_DETAILS_FAIL'
userActions.js
export const detailsUser = (userId) => async(dispatch, getState)=>{ dispatch({ type: USER_DETAILS_REQUEST, payload:userId }) const {userSignin:{userInfo}} = getState(); try{ const {data} = await axios.get(`/api/users/${userId}`, { headers:{Authorization: `Bearer ${userInfo.token}`} }) dispatch({ type:USER_DETAILS_SUCCESS, payload:data }) }catch(error){ const message = error.response && error.response.data.message ? error.response.data.message : error.message dispatch({ type: USER_DETAILS_FAIL, payload:message }) } }
userReducers.js
export const userDetailsReducer = (state = {loading:true} ,action)=>{ switch(action.type){ case USER_DETAILS_REQUEST: return {loading:true} case USER_DETAILS_SUCCESS: return {loading:false, user:action.payload} case USER_DETAILS_FAIL: return {loading:false, error:action.payload} default: return state; } }
store.js
const reducer = combineReducers({ productList: productListReducer, productDetails : productDetailsReducer, cart:cartReducer, userSignin: userSigninReducer, userRegister: userRegisterReducer, orderCreate:orderCreateReducer, orderDetails:orderDetailsReducer, orderPay: orderPayReducer, orderMineList:orderMineListReducer, userDetails:userDetailsReducer })
ProfileScreen.js
import React, { useEffect } from 'react' import {useDispatch, useSelector} from 'react-redux' import { detailsUser } from '../actions/userActions' import LoadingBox from '../components/LoadingBox' import MessageBox from '../components/MessageBox' function ProfileScreen() { const userSignin = useSelector(state => state.userSignin) const {userInfo} = userSignin const userDetails = useSelector(state => state.userDetails) const {loading, error, user}= userDetails const dispatch = useDispatch() useEffect(()=>{ dispatch(detailsUser(userInfo._id)); },[dispatch, userInfo._id]) const submitHandler = (e) =>{ e.preventDefault(); } return ( <div> <form onSubmit={submitHandler} className="form"> <div> <h1>User Profile</h1> </div> <div> { loading? <LoadingBox></LoadingBox> : error ? <MessageBox variant='danger'>{error}</MessageBox> : <> <div> <label htmlFor="name">Name</label> <input id = "name" placeholder="Enter name" type="name" value = {user.name} /> </div> <div> <label htmlFor="email">Email</label> <input id = "email" placeholder="Enter email" type="email" value = {user.email} /> </div> <div> <label htmlFor="password">Password</label> <input id = "password" placeholder="Enter password" type="password" /> </div> <div> <label htmlFor="confirmPassword">Confirm Password</label> <input id = "confirmPassword" placeholder="Enter confirmPassword" type="password" /> </div> <div> <label ></label> <button className="primary" type="submit">Update</button> </div> </> } </div> </form> </div> ) } export default ProfileScreen
app.js에 추가
import ProfileScreen from './screens/ProfileScreen'; <ul className="dropdown-content"> <li> <Link to = "/profile">User Profile</Link> </li> <li> <Link to = "/orderhistory">Order History</Link> </li> <li> <Link to="#signout" onClick={signoutHandler}>Sing Out</Link> </li> </ul> </div> <Route path="/profile" component={ProfileScreen} exact></Route>
여기서 추가로 프론트엔트 폴더에 jsconfig.json 파일을 추가하면 auto import가 잘 작동된다.
{ "compilerOptions": { "target":"es6" }, "exclude": ["node_modules"] }
이제 업데이트 기능을 추가하자.
ProfileScreen.js에서 useEffect 파라미터로 user를 줌으로서
user에 변경사항이 생기면 자동으로 useEffect가 실행되도록 한다.
타입창에 정보가 변경될때마다 이름, 이메일을 다시 지정해준다.
업데이트 버튼을 누르면 그대로 정보를 다시 db에 저장한다.
const [name, setname] = useState("") const [email, setemail] = useState("") const [password, setpassword] = useState("") const [confirmPassword, setconfirmPassword] = useState("") const submitHandler = (e) =>{ e.preventDefault(); if(password!==confirmPassword){ alert('Password and Confirm Password Are Not Matched') }else{ dispatch(updateUserProfile({userId:user._id, name, email, password})) } } <div> { loading? <LoadingBox></LoadingBox> : error ? <MessageBox variant='danger'>{error}</MessageBox> : <> <div> <label htmlFor="name">Name</label> <input id = "name" placeholder="Enter name" type="text" value = {name} onChange={e=>setname(e.target.value)}/> </div> <div> <label htmlFor="email">Email</label> <input id = "email" placeholder="Enter email" type="email" value = {email} onChange={e=>setemail(e.target.value)} /> </div> <div> <label htmlFor="password">Password</label> <input id = "password" placeholder="Enter password" type="password" onChange={e=>setpassword(e.target.value)} /> </div> <div> <label htmlFor="confirmPassword">Confirm Password</label> <input id = "confirmPassword" placeholder="Enter confirmPassword" type="password" onChange={e=>setconfirmPassword(e.target.value)} /> </div> <div> <label ></label> <button className="primary" type="submit">Update</button> </div> </> } </div>
이제 프로필을 업데이트하는 기능을 만들자.
userConstants.js
export const USER_UPDATE_PROFILE_REQUEST='USER_UPDATE_PROFILE_REQUEST' export const USER_UPDATE_PROFILE_SUCCESS='USER_UPDATE_PROFILE_SUCCESS' export const USER_UPDATE_PROFILE_FAIL='USER_UPDATE_PROFILE_FAIL' export const USER_UPDATE_PROFILE_RESET='USER_UPDATE_PROFILE_RESET'
userActions.js
변경하고 나서는 사용자 정보가 변경될 수 있으므로, 다시 로그인 시키고
로컬저장소에도 사용자 정보로 다시 저장한다.
export const updateUserProfile = (user)=> async (dispatch, getState)=>{ dispatch({ type: USER_UPDATE_PROFILE_REQUEST, payload:user }) const {userSignin:{userInfo}} = getState(); try{ const {data} = await axios.put(`/api/users/profile`, user, { headers:{Authorization: `Bearer ${userInfo.token}`} }) dispatch({ type: USER_UPDATE_PROFILE_SUCCESS, payload:data }) dispatch({ type:USER_SIGNIN_SUCCESS, payload:data }) localStorage.setItem('userInfo', JSON.stringify(data)) }catch(error){ const message = error.response && error.response.data.message ? error.response.data.message : error.message dispatch({ type: USER_UPDATE_PROFILE_FAIL: payload:message }) } }
userReducers.js
export const userUpdateProfileReducer = (state={}, action)=>{ switch(action.type){ case USER_UPDATE_PROFILE_REQUEST: return {loading:true} case USER_UPDATE_PROFILE_SUCCESS: return {loading:false, success:true} case USER_UPDATE_PROFILE_FAIL: return {loading:false, error:action.payload} case USER_UPDATE_PROFILE_RESET: return {}; default: return state; } }
store.js
const reducer = combineReducers({ productList: productListReducer, productDetails : productDetailsReducer, cart:cartReducer, userSignin: userSigninReducer, userRegister: userRegisterReducer, orderCreate:orderCreateReducer, orderDetails:orderDetailsReducer, orderPay: orderPayReducer, orderMineList:orderMineListReducer, userDetails:userDetailsReducer, userUpdateProfile:userUpdateProfileReducer })
ProfileScreen.js
디테일 보내주기 전에 프로필 한번 리셋하고 나서 가져온다.
import { USER_UPDATE_PROFILE_RESET } from '../constants/userConstants' function ProfileScreen() { const userUpdateProfile = useSelector(state => state.userUpdateProfile) const {success: successUpdate, error:errorUpdate, loading: loadingUpdate} = userUpdateProfile const dispatch = useDispatch() useEffect(()=>{ if(!user){ dispatch({ type:USER_UPDATE_PROFILE_RESET }) dispatch(detailsUser(userInfo._id)); }else{ setname(user.name) setemail(user.email) } },[dispatch, userInfo._id, user]) const submitHandler = (e) =>{ e.preventDefault(); if(password!==confirmPassword){ alert('Password and Confirm Password Are Not Matched') }else{ dispatch(updateUserProfile({userId:user._id, name, email, password})) } } return ( <div> <form onSubmit={submitHandler} className="form"> <div> <h1>User Profile</h1> </div> <div> { loading? <LoadingBox></LoadingBox> : error ? <MessageBox variant='danger'>{error}</MessageBox> : <> { loadingUpdate && <LoadingBox></LoadingBox>} { errorUpdate && <MessageBox variant='danger'>{errorUpdate}</MessageBox>} {successUpdate && <MessageBox variant="success">Profile Updated Successfully</MessageBox>} <div> <label htmlFor="name">Name</label> <input id = "name" placeholder="Enter name" type="text" value = {name} onChange={e=>setname(e.target.value)}/> </div> <div> <label htmlFor="email">Email</label> <input id = "email" placeholder="Enter email" type="email" value = {email} onChange={e=>setemail(e.target.value)} /> </div> <div> <label htmlFor="password">Password</label> <input id = "password" placeholder="Enter password" type="password" onChange={e=>setpassword(e.target.value)} /> </div> <div> <label htmlFor="confirmPassword">Confirm Password</label> <input id = "confirmPassword" placeholder="Enter confirmPassword" type="password" onChange={e=>setconfirmPassword(e.target.value)} /> </div> <div> <label ></label> <button className="primary" type="submit">Update</button> </div> </> } </div> </form> </div> ) } export default ProfileScreen
이제 유저라우터에 추가해주자.
userRouter.put('/profile', isAuth, expressAsyncHandler(async(req, res)=>{ const user = await User.findById(req.user._id); if(user){ user.name = req.body.name || user.nmae; user.email = req.body.email||user.email; if(req.body.password){ user.password = bcrypt.hashSync(req.body.password, 8); } const updateUser = await user.save(); res.send({ _id:updateUser._id , email: updateUser.email, name: updateUser.name, isAdmin: updateUser.isAdmin, token: generateToken(updateUser), }) } }))
이 프로필 스크린에서 로그아웃하면 다시 sign in 페이지로 가도록 지정할 것이다.
이 프로필 페이지는 로그인 된 유저들만 볼 수 있도록 privateRoute로 넣자.
components> PrivateRoute.js
import React from 'react' import { useSelector } from 'react-redux' import { Redirect, Route } from 'react-router-dom' function PrivateRoute({component:Component, ...rest}) { const userSignin = useSelector(state=>userSignin) const {userInfo} = userSignin return ( <Route {...rest} render = {(props)=>userInfo? (<Component {...props}></Component>): ( <Redirect to="/signin"/> )} ></Route> ) } export default PrivateRoute
App.js를 다음과 같이 수정한다.
import PrivateRoute from './components/PrivateRoute'; <PrivateRoute path="/profile" component={ProfileScreen} exact></PrivateRoute>
이제 관리자들만 볼 수있는 링크를 따로 만들어보자.
app.js에 admin이라면 볼 수 있는 드롭다운을 만들어준다.
{ userInfo ? ( <div className="dropdown"> <Link to="#">{userInfo.name} <i className="fa fa-caret-down"></i> </Link> <ul className="dropdown-content"> <li> <Link to = "/profile">User Profile</Link> </li> <li> <Link to = "/orderhistory">Order History</Link> </li> <li> <Link to="#signout" onClick={signoutHandler}>Sing Out</Link> </li> </ul> </div> ) : (<Link to="/signin">Sign In</Link>) } {userInfo && userInfo.isAdmin && ( <div className="dropdown"> <Link to="#admin">Admin {' '}<i className="fa fa-caret-down"></i></Link> <ul className="dropdown-content"> <li> <Link to = "/dashboard">Dashboard</Link> </li> <li> <Link to = "/productlist">Products</Link> </li> <li> <Link to = "/orderlist">Order</Link> </li> <li> <Link to = "/userlist">Users</Link> </li> </ul> </div> )}
private라우트를 만든것 처럼components>AdminRoute.js를 만든다.
import React from 'react' import { useSelector } from 'react-redux' import { Redirect, Route } from 'react-router-dom' function PrivateRoute({component:Component, ...rest}) { const userSignin = useSelector(state=>state.userSignin) const {userInfo} = userSignin return ( <Route {...rest} render = {(props)=>userInfo && userInfo.isAdmin? (<Component {...props}></Component>): ( <Redirect to="/signin"/> )} ></Route> ) } export default PrivateRoute
백엔드로 가서 라우트 생성해준다.
isAuth처럼 utils.js에 미들웨어를 만들어준다.
export const isAdmin = (req, res, next)=>{ if(req.user && req.user.isAdmin){ next(); }else{ res.status(401).send({message:'Invalid Admin Token'}) } }
'NODE.JS' 카테고리의 다른 글
아마존 E-commerce 클론 -22) Admin order관리페이지 만들기 (0) 2021.05.28 아마존 E-commerce 클론 -21) Admin product관리페이지 만들기 (0) 2021.05.28 아마존 E-commerce 클론 -19) Order History screen 만들기 (0) 2021.05.28 아마존 E-commerce 클론 -18) payment Button 만들기 (0) 2021.05.28 아마존 E-commerce 클론 -17) order Screen 만들기 (0) 2021.05.28