아마존 E-commerce 클론 -23) Admin user관리페이지 만들기
모든 유저의 정보를 가지고 오는 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])