ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 아마존 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'})
        }
    }

     

     

     

     

     

     

     

     

     

     

     

     

Designed by Tistory.