NODE.JS

아마존 E-commerce 클론 -23) Admin user관리페이지 만들기

dodop 2021. 5. 29. 11:32

 

 

모든 유저의 정보를 가지고 오는 userRouter를 만들어보자. 

userRouter.get('/', isAuth,isAdmin, expressAsyncHandler(async(req, res)=>{  
  const users = await User.find({})
  res.send(users);

}))

 

UserListScreen.js

import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import LoadingBox from '../components/LoadingBox'
import MessageBox from '../components/MessageBox'

function UserListScreen() {
    const dispatch = useDispatch()

    useEffect(() => {
        dispatch(listUsers())
    }, [dispatch])

    return (
        <div>
            <h1>Users</h1>
            {
                loading? <LoadingBox></LoadingBox>:
                error? <MessageBox variant='danger'>{error}</MessageBox> :
                (
                    <table className="table">
                        <thead>
                            <tr>
                                <th>ID</th>
                                <th>NAME</th>
                                <th>EMAIL</th>
                                <th>IS SELLER</th>
                                <th>IS ADMIN</th>
                                <th>ACTIONS</th>
                            </tr>
                        </thead>
                        <tbody>
                            {
                                users.map(user=>(
                                    <tr key = {user._id}>
                                        <td>{user._id}</td>
                                        <td>{user.name}</td>
                                        <td>{user.email}</td>
                                        <td>{user.isSeller? 'YES': 'NO'}</td>
                                        <td>{user.isAdmin? 'YES': 'NO'}</td>
                                        <td>
                                            <button className="small">Edit</button>
                                            <button className="small">Delete</button>
                                        </td>
                                    </tr>
                                ))
                            }
                        </tbody>
                    </table>
                )
            }
        </div>
    )
}

export default UserListScreen

 

userModel에 isseller 특징을 추가해주자. 

    isSeller:{
        type:Boolean,
        default:false,
        required:true
    }

 

 

 

 

이제userConstants.js

export const USER_LIST_REQUEST = 'USER_LIST_REQUEST'
export const USER_LIST_SUCCESS = 'USER_LIST_SUCCESS'
export const USER_LIST_FAIL = 'USER_LIST_FAIL'

userActions.js

export const listUsers = () =>async(dispatch, getState)=>{
    dispatch({
        type: USER_LIST_REQUEST
    })
    const {userSignin:{userInfo}} = getState();
    try{
        const {data} = await axios.get(`/api/users`, {
            headers:{Authorization: `Bearer ${userInfo.token}`}
        })
        dispatch({
            type: USER_LIST_SUCCESS,
            payload:data
        })
    }catch(error){
        const message = error.response && error.response.data.message
        ? error.response.data.message
        : error.message
        dispatch({
            type: USER_LIST_FAIL,
            payload:message
        })
    }
}

 

userReducers.js

export const userListReducter = (state = {loading:true} ,action)=>{
    switch(action.type){
        case USER_LIST_REQUEST:
            return {loading:true}
        case USER_LIST_SUCCESS:
            return {loading:false, users:action.payload}
        case USER_LIST_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,
    userUpdateProfile:userUpdateProfileReducer,
    productCreate:productCreateReducer,
    productUpdate:productUpdateReducer,
    productDelete:productDeleteReducer,
    orderList:orderListReducer,
    orderDelete:orderDeleteReducer,
    orderDeliver:orderDeliverReducer,
    userList:userListReducter
})

UserListScreen.js

import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { listUsers } from '../actions/userActions'
import LoadingBox from '../components/LoadingBox'
import MessageBox from '../components/MessageBox'

function UserListScreen() {
    const userList = useSelector(state => state.userList)
    const {loading, error, users} = userList
    const dispatch = useDispatch()

    useEffect(() => {
        dispatch(listUsers())
    }, [dispatch])

    return (
        <div>
            <h1>Users</h1>
            {
                loading? <LoadingBox></LoadingBox>:
                error? <MessageBox variant='danger'>{error}</MessageBox> :
                (
                    <table className="table">
                        <thead>
                            <tr>
                                <th>ID</th>
                                <th>NAME</th>
                                <th>EMAIL</th>
                                <th>IS SELLER</th>
                                <th>IS ADMIN</th>
                                <th>ACTIONS</th>
                            </tr>
                        </thead>
                        <tbody>
                            {
                                users.map(user=>(
                                    <tr key = {user._id}>
                                        <td>{user._id}</td>
                                        <td>{user.name}</td>
                                        <td>{user.email}</td>
                                        <td>{user.isSeller? 'YES': 'NO'}</td>
                                        <td>{user.isAdmin? 'YES': 'NO'}</td>
                                        <td>
                                            <button className="small">Edit</button>
                                            <button className="small">Delete</button>
                                        </td>
                                    </tr>
                                ))
                            }
                        </tbody>
                    </table>
                )
            }
        </div>
    )
}

export default UserListScreen

 

 

app.js에 스크린 추가

  import UserListScreen from './screens/UserListScreen';
            <AdminRoute path="/userlist" component={UserListScreen} exact></AdminRoute>          

 

 

 

이제 delete기능을 활성화 시키자. 

 

 

api를 먼저 만들자. 

userRouter.js

userRouter.delete('/:id', isAuth, isAdmin, expressAsyncHandler(async(req, res)=>{
  const user = await User.findById(req.params.id)
  if(user){
    const deletedUser = await user.remove();
    res.send({message: 'User Deleted', user:deletedUser})
  }else{
    res.status(404).send({message:'User Not Found'})
  }
}))

 

 

userConstants.js

export const USER_DELETE_REQUEST = 'USER_DELETE_REQUEST'
export const USER_DELETE_SUCCESS = 'USER_DELETE_SUCCESS'
export const USER_DELETE_FAIL = 'USER_DELETE_FAIL'
export const USER_DELETE_RESET = 'USER_DELETE_RESET'

 

userActions.js

export const deleteUser = (userId) =>async(dispatch, getState)=>{
    dispatch({
        type: USER_DELETE_REQUEST,
        payload:userId
    })
    const {userSignin:{userInfo}} = getState();
    try{
        const {data} = await axios.delete(`/api/users/${userId}`, {
            headers:{Authorization: `Bearer ${userInfo.token}`}
        })
        dispatch({
            type: USER_DELETE_SUCCESS,
            payload:data
        })
    }catch(error){
        const message = error.response && error.response.data.message
        ? error.response.data.message
        : error.message
        dispatch({
            type: USER_DELETE_FAIL,
            payload:message
        })
    }
}

userReducers.js

새로운페이지로의 이동이 아니기 때문에 (버튼클릭) state=loading 넣어줄 필요 없다. 

export const userDeleteReducer = (state={}, action)=>{
    switch(action.type){
        case USER_DELETE_REQUEST:
            return {loading:true}
        case USER_DELETE_SUCCESS:
            return {loading:false, success:true}
        case USER_DELETE_FAIL:
            return {loading:false, error:action.payload}
        case USER_DELETE_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,
    productCreate:productCreateReducer,
    productUpdate:productUpdateReducer,
    productDelete:productDeleteReducer,
    orderList:orderListReducer,
    orderDelete:orderDeleteReducer,
    orderDeliver:orderDeliverReducer,
    userList:userListReducter,
    userDelete:userDeleteReducer
})

userListScreen.js

 

 

import { deleteUser, listUsers } from '../actions/userActions'
    const userDelete = useSelector(state => state.userDelete)
    const {loading:loadingDelete, error:errorDelete, success:successDelete} = userDelete


    useEffect(() => {
        dispatch(listUsers())
    }, [dispatch, successDelete])//삭제가되면 리스트 다시 가져오도록 설정
    
    const deleteHandler = (user)=>{
        if(window.confirm('Are you sure to delete?')){
            dispatch(deleteUser(user._id))
        }
    }
    
    
    
    return (
        <div>
            <h1>Users</h1>
            {loadingDelete && <LoadingBox></LoadingBox>}
            {errorDelete && <MessageBox variant='danger'>{errorDelete}</MessageBox>}
            {successDelete && <MessageBox variant='success'>User Deleted Successfully</MessageBox>}
            {
            
            
            
            
            
                                        <td>
                                            <button type = "button" className="small" >Edit</button>
                                            <button type = "button" className="small" onClick ={()=>deleteHandler(user)}  >Delete</button>
                                        </td>

 

여기서 추가로 admin계정은 삭제하지 못하도록 설정하자. 

userRouter.js

userRouter.delete('/:id', isAuth, isAdmin, expressAsyncHandler(async(req, res)=>{
  const user = await User.findById(req.params.id)
  if(user){
    if(user.email === 'admin@example.com'){
      res.status(400).send({message:'Can Not Delete Admin User'})
      return;
    }
    const deletedUser = await user.remove();
    res.send({message: 'User Deleted', user:deletedUser})
  }else{
    res.status(404).send({message:'User Not Found'})
  }
}))
export default userRouter;

난 isAdim ===true일때 삭제 못하는 것으로 설정하겠다. 

userRouter.delete('/:id', isAuth, isAdmin, expressAsyncHandler(async(req, res)=>{
  const user = await User.findById(req.params.id)
  if(user){
    if(user.isAdmin === true){
      res.status(400).send({message:'Can Not Delete Admin User'})
      return;
    }
    const deletedUser = await user.remove();
    res.send({message: 'User Deleted', user:deletedUser})
  }else{
    res.status(404).send({message:'User Not Found'})
  }
}))
export default userRouter;

 

 

이제 edit user 기능을 만들자. 

우선UserEditScreen.js를 만들자(형식잡기)

import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { detailsUser } from '../actions/userActions'
import LoadingBox from '../components/LoadingBox'
import MessageBox from '../components/MessageBox'

function UserEditScreen(props) {
    const userId = props.match.params.id

    const [name, setName] = useState('')
    const [email, setEmail] = useState('')
    const [isSeller, setIsSeller] = useState(false)
    const [isAdmin, setIsAdmin] = useState(false)

    const userDetails = useSelector(state => state.userDetails)
    const {loading, error,user } = userDetails

    const dispatch = useDispatch()
    useEffect(() => {
        if(!user){
            dispatch(detailsUser(userId));
        }else{
            setName(user.name);
            setEmail(user.email)
            setIsSeller(user.isSeller)
            setIsAdmin(user.isAdmin)
        }
    const submitHandler = (e)=>{
        e.preventDefault();

    }
    return (
        <div>
            <form className="form" onSubmit={submitHandler}>
                <h1>Edit User</h1>
                <div>
                    {loading? <LoadingBox></LoadingBox> :
                     error? <MessageBox variant="danger">{error}</MessageBox> :
                     (
                         <>
                         <div>
                             <label htmlFor="name">Name</label>
                             <input type="text" id="name" placeholder="Enter name" value={name} onChange={(e)=> setName(e.target.value)} />
                         </div>
                         <div>
                             <label htmlFor="email">Email</label>
                             <input type="email" id="email" placeholder="Enter email" value={email} onChange={(e)=> setEmail(e.target.value)} />
                         </div>
                         <div>
                             <label htmlFor="isSeller">Is Seller</label>
                             <input type="checkbox" id="isSeller" checked={isSeller} onChange={(e)=> setIsSeller(e.target.checked)} />
                         </div>
                         <div>
                             <label htmlFor="isAdmin">Is Admin</label>
                             <input type="checkbox" id="isAdmin" checked={isAdmin} onChange={(e)=> setIsAdmin(e.target.checked)} />
                         </div>
                         <div>
                             <button className="primary" type="submit">Update</button>
                         </div>
                         </>
                     )
                    }
                </div>
            </form>
        </div>
    )
}

export default UserEditScreen

 

app.js에 스크린 추가

import UserEditScreen from './screens/UserEditScreen';

	<AdminRoute path="/user/:id/edit" component={UserEditScreen} exact></AdminRoute>          

 

userListScreen에서 edit 버튼 누르면 페이지를 이동하도록 해주자. 

function UserListScreen(props) {

                                            <button type = "button" className="small" onClick={()=>props.history.push(`/user/${user._id}/edit`)} >Edit</button>

 

유저정보를 업데이트하는 api를 만들자. 

userRouter.put(
  '/:id',
  isAuth,
  isAdmin,
  expressAsyncHandler(async (req, res) => {
    const user = await User.findById(req.params.id);
    if (user) {
      user.name = req.body.name || user.name;
      user.email = req.body.email || user.email;
      user.isSeller =req.body.isSeller === user.isSeller ? user.isSeller : req.body.isSeller; //Boolean(req.body.isSeller)     
      user.isAdmin =req.body.isAdmin === user.isAdmin ? user.isAdmin : req.body.isAdmin;//Boolean(req.body.isAdmin)
      
      const updatedUser = await user.save();
      res.send({ message: 'User Updated', user: updatedUser });
    } else {
      res.status(404).send({ message: 'User Not Found' });
    }
  })
);

export default userRouter;

 

userConstants.js

export const USER_UPDATE_REQUEST = 'USER_UPDATE_REQUEST'
export const USER_UPDATE_SUCCESS = 'USER_UPDATE_SUCCESS'
export const USER_UPDATE_FAIL = 'USER_UPDATE_FAIL'
export const USER_UPDATE_RESET = 'USER_UPDATE_RESET'

userActions.js

export const updateUser = (user)=> async (dispatch, getState)=>{
    dispatch({
        type: USER_UPDATE_REQUEST,
        payload:user
    })
    const {userSignin:{userInfo}} = getState();
    try{
        const {data} = await axios.put(`/api/users/${user._id}`, user, {
            headers:{Authorization: `Bearer ${userInfo.token}`}
        })
        dispatch({
            type: USER_UPDATE_SUCCESS,
            payload:data
        })

    }catch(error){
        const message = error.response && error.response.data.message
        ? error.response.data.message
        : error.message
        dispatch({
            type: USER_UPDATE_FAIL,
            payload:message
        })
    }

}

 

userReducers.js

   export const userUpdateReducer = (state={}, action)=>{
    switch(action.type){
        case USER_UPDATE_REQUEST:
            return {loading:true}
        case USER_UPDATE_SUCCESS:
            return {loading:false, success:true}
        case USER_UPDATE_FAIL:
            return {loading:false, error:action.payload}
        case USER_UPDATE_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,
    productCreate:productCreateReducer,
    productUpdate:productUpdateReducer,
    productDelete:productDeleteReducer,
    orderList:orderListReducer,
    orderDelete:orderDeleteReducer,
    orderDeliver:orderDeliverReducer,
    userList:userListReducter,
    userDelete:userDeleteReducer,
    userUpdate:userUpdateReducer
})

 

userEditScreen.js

import { useDispatch, useSelector } from 'react-redux'
import { detailsUser, updateUser } from '../actions/userActions'

import { USER_UPDATE_RESET } from '../constants/userConstants'

function UserEditScreen(props) {

    const userUpdate = useSelector(state => state.userUpdate)
    const {loading:loadingUpdate, error:errorUpdate, success:successUpdate} = userUpdate

    const dispatch = useDispatch()
    useEffect(() => {
        if(successUpdate){
            dispatch({
                type:USER_UPDATE_RESET
            })
            props.history.push('/userList')
        }
        if(!user){
            dispatch(detailsUser(userId));
        }else{
            setName(user.name);
            setEmail(user.email)
            setIsSeller(user.isSeller)
            setIsAdmin(user.isAdmin)
        }
    }, [dispatch,props.history, user, userId, successUpdate])

    const submitHandler = (e)=>{
        e.preventDefault();
        dispatch(updateUser({_id:userId, name, email, isSeller, isAdmin}))

    }
    return (
        <div>
            <form className="form" onSubmit={submitHandler}>
                <h1>Edit User</h1>
                <div>
                    {loadingUpdate && <LoadingBox></LoadingBox>}
                    {errorUpdate && <MessageBox variant='danger'>{errorUpdate}</MessageBox>}
                    {loading? <LoadingBox></LoadingBox> :
                     error? <MessageBox variant="danger">{error}</MessageBox> :

 

추가로 유저 리스트를 가지고 오고 나면 디테일을 리셋해주는 기능을 할건데, 

user_DETAILS_RESET을 추가로 만들어준다. 

export const USER_DETAILS_RESET = 'USER_DETAILS_RESET'

 

추가

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}
        case USER_DETAILS_RESET:
            return {loading:true};
        default:
            return state;
    }
}

userListScreen을 다음과 같이 수정해준다. 

    useEffect(() => {
        dispatch(listUsers())
        dispatch({
            type:USER_DETAILS_RESET
        })
    }, [dispatch, successDelete])