NODE.JS

아마존 E-commerce 클론 -27) Search Box ,Search Filter기능 , Side bar, sort & Filter 만들기

dodop 2021. 5. 29. 22:42

 

우선 serchbox component를 만들자. 

components>SearchBox.js

import React, { useState } from 'react'

function SearchBox(props) {
    const [name, setName] = useState('')
    const submitHandler=(e)=>{
        e.preventDefault();
        props.history.push(`/search/name/${name}`)
    }
    return (
        <form onSubmit={submitHandler} className="search">
            <div className="row">
                <input type="text" name="q" id = "q" onChange={e=>setName(e.target.value)} />
                <button className="primary" type="submit"><i className="fa fa-search"></i></button>
            </div>
        </form>
    )
}

export default SearchBox

 

app.js에 추가하는데 route의 render를 이용해서 history기능도 사용할 수 있도록 한다. 

            <div>
              <Link className="brand" to="/">amazona</Link>
            </div>
            <div>
              <Route render = {({history})=>( <SearchBox history={history}></SearchBox>)}></Route>
            </div>

 

style추가 

/* Search */
.search button{
  border-radius:0  0.5rem 0.5rem 0;
  border-right: none;
  margin-right: 0.5rem;
}
.search input{ 
  border-radius: 0.5rem 0 0 0.5rem;
  border-right: none;
  margin-left: 0.5rem;
}

 

 

검색하면 주소가 잘 이동한다. 

 

 

 

이제 SearchScreen.js를 만들어준다. 

상품리스트를 가져와서 보여주는 전체적인 템플릿을 만든다. 

import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useParams } from 'react-router'
import { listProducts } from '../actions/productActions'
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import Product from '../components/Product';


function SearchScreen(props) {
    const {name = 'all'} = useParams();
    const productList = useSelector(state => state.productList)
    const {loading, error, products} = productList
    const dispatch = useDispatch()
    useEffect(() => {
        dispatch(listProducts({name:name!=='all'? name: ''}))
    }, [dispatch,name])
    return (
        <div>
            <div className="row top">
                {loading? <LoadingBox></LoadingBox> :
                error? <MessageBox variant='danger'>{error}</MessageBox> :
                <div>
                    {products.length} Results
                </div>
                }
            </div>
            <div className="row">
                <div className="co-1">
                    <h3>Department</h3>
                    <ul>
                        <li>Caterory 1</li>
                    </ul>
                </div>
                <div className="col-3">
                    {loading? <LoadingBox></LoadingBox> :
                    error? <MessageBox variant='danger'>{error}</MessageBox> :
                    <div>
                        {products.length===0 &&  <MessageBox>No Product Found</MessageBox>}
                        <div className="row center">
                            {products.map(product=>(
                                <Product key={product._id}  product = {product}/> 
                            ))}
                        </div>
                    </div>
                    }
                </div>
            </div>
        </div>
    )
}

export default SearchScreen

 

 

app.js에 라우트를 추가해준다. 

import SearchScreen from './screens/SearchScreen';

              <Route path="/search/name/:name?" component={SearchScreen} exact></Route>

 

 

 

이제 상품이 일치하는 상품만 나오도록 설정하자. 

 

productActions.js

export const listProducts = ({seller='', name=''}) =>async(dispatch)=>{
    dispatch({
        type:PRODUCT_LIST_REQUEST
    });
    try{
        const {data} = await Axios.get(`/api/products?seller=${seller}&name=${name}`);
        dispatch({type:PRODUCT_LIST_SUCCESS, payload:data});
    }catch(error){
        dispatch({type:PRODUCT_LIST_FAIL, payload:error.message});
    }
}

 

 

productRouter.js에 다음과 같은 검사항목을 추가해준다. 

productRouter.get('/' ,expressAsyncHandler(async(req, res)=>{
    const seller = req.query.seller || ''
    const name = req.query.name || ''
    const sellerFilter = seller? {seller} :{};
    const nameFilter = name? {name:{$regex:name, $options:'i'}} :{};
    const products = await Product.find({...sellerFilter, ...nameFilter}).populate('seller', 'seller.name seller.logo');
    res.send(products);
}));

맞는 검사항목만 보여준다. 

 

이제 카테고리별로 나타내도록 필터를 만들자. 

 

 

먼저 productrouter에 api를 만든다. 

(list 아래에 만들기)

productRouter.get('/categories' ,expressAsyncHandler(async(req, res)=>{
    const categories = await Product.find().distinct('category')
    res.send(categories)
}));

 

productConstants로 먼저 가보자. 

export const PRODUCT_CATEGORY_LIST_REQUEST = 'PRODUCT_CATEGORY_LIST_REQUEST';
export const PRODUCT_CATEGORY_LIST_SUCCESS = 'PRODUCT_CATEGORY_LIST_SUCCESS';
export const PRODUCT_CATEGORY_LIST_FAIL = 'PRODUCT_CATEGORY_LIST_FAIL';

 

productActions

 

productlist와 비슷하므로 복사해서 수정해준다. 

export const listProductsCategories = () =>async(dispatch)=>{
    dispatch({
        type:PRODUCT_CATEGORY_LIST_REQUEST
    });
    try{
        const {data} = await Axios.get(`/api/products/categories`);
        dispatch({type:PRODUCT_CATEGORY_LIST_SUCCESS, payload:data});
    }catch(error){
        dispatch({type:PRODUCT_CATEGORY_LIST_FAIL, payload:error.message});
    }
}

 

 

 

productReducers

export const productCategoryListReducer = (state = { loading:true, categories: []}, action)=>{
    switch(action.type){
        case PRODUCT_CATEGORY_LIST_REQUEST:
            return {loading: true};
        case PRODUCT_CATEGORY_LIST_SUCCESS:
            return {loading:false, categories:action.payload};
        case PRODUCT_CATEGORY_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:userListReducer,
    userDelete:userDeleteReducer,
    userUpdate:userUpdateReducer,
    userTopSellersList:userTopSellerListReducer,
    productCategoryList:productCategoryListReducer
})

 

 

 

 

App.js안에 필터항목을 나타내보자.(sidebar로 나타낼 것이다) 

import { listProductsCategories } from './actions/productActions';

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

 

searchScreen에도 보이도록 하자. 

const {name = 'all', category='all'} = useParams();

const productCategoryList = useSelector(state => state.productCategoryList)
const {loading:loadingCategories, error:errorCategories, categories} = productCategoryList

useEffect(() => {
        dispatch(listProducts({name:name!=='all'? name: '', category:category !=='all'? category:''}))
    }, [category, dispatch,name])


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


const getFilterUrl = (filter) =>{
    const filterCategory = filter.category ||category;
    const filterName = filter.name ||name;
    return `/search/category/${filterCategory}/name/${filterName}`
    }
    
    
    		<div className="row top">
                <div className="col-1">
                    <h3>Department</h3>
                    {loadingCategories? <LoadingBox></LoadingBox> :
                    errorCategories? <MessageBox variant='danger'>{errorCategories}</MessageBox> :
                    (
                        <ul>
                            {categories.map(c=>
                                (
                                    <li key = {c}>
                                        <Link to ={getFilterUrl({category:c})} className={c===category? 'active':''}>{c}</Link>
                                    </li>
                                ))
                            }
                        </ul>
                    )}
                </div>
    
    
 

 

 

 

app.js에 루트 추가해준다. 

              <Route path="/search/category/:category" component={SearchScreen} exact></Route>
              <Route path="/search/category/:category/name/:name" component={SearchScreen} exact></Route>

 

productActions에서 다음과 같이 카테고리 항목도 추가해준다. 

export const listProducts = ({seller='', name='', category = ''}) =>async(dispatch)=>{
    dispatch({
        type:PRODUCT_LIST_REQUEST
    });
    try{
        const {data} = await Axios.get(`/api/products?seller=${seller}&name=${name}&category=${category}`);
        dispatch({type:PRODUCT_LIST_SUCCESS, payload:data});
    }catch(error){
        dispatch({type:PRODUCT_LIST_FAIL, payload:error.message});
    }
}

 

라우터도 수정해준다. 

productRouter.get('/' ,expressAsyncHandler(async(req, res)=>{
    const seller = req.query.seller || ''
    const name = req.query.name || ''
    const category = req.query.category || ''
    const sellerFilter = seller? {seller} :{};
    const nameFilter = name? {name:{$regex:name, $options:'i'}} :{};
    const categoryFilter = category? {category} :{};
    const products = await Product.find({...sellerFilter, ...nameFilter, ...categoryFilter}).populate('seller', 'seller.name seller.logo');
    res.send(products);
}));

 

 

 

검색을 하고나서 카테고리들어가면 카테고리 안의 name일치하는 것 찾아준다.

 

이제 app.js에 카테고리 사이드바를 넣자. 

 

 

  const [sidebarIsOpen, setSidebarIsOpen] = useState(false)


  return (
    <BrowserRouter >
    <div className="grid-container">
          <header className="row">
            <div>
              <button type="button" className="open-sidebar" onClick={()=>setSidebarIsOpen(true)}>
                <i className="fa fa-bars"></i>
              </button>
              <Link className="brand" to="/">amazona</Link>
              
              
              
              ...
              
              
              </header>
            <aside className={sidebarIsOpen? 'open': ''}>
                  <ul className="categories">
                    <li>
                      <strong>Categories</strong>
                      <button type="button" className="close-sidebar" onClick={()=>setSidebarIsOpen(false)}>
                          <i className="fa fa-close"></i>
                      </button>
                  </li>
                </ul>
          </aside>
          <main>

스타일링하자.

/* Aside */
aside {
  position: fixed;
  width: 30rem;
  height: 100%;
  background-color: #efefef;
  z-index: 1000 ;
  transform: translate(-30rem);
  transition: all 0.5s;

}
aside.open{
  transform: translate(0) ;
}
button.open-sidebar{
  font-size: 3rem;
  padding:0.2rem 0.5rem;
  margin:0 0.5rem;
  background:none;
  color: #ffffff;
  cursor:pointer
}
button.open-sidebar:hover{
  border-color: #ffffff;
}
aside ul{
  padding:0;
  list-style: none;
}
aside li{
  display: flex;
  justify-content: space-between;
  padding:1rem;
}
button.close-sidebar{
  padding:0.3rem 0.8rem;
}





/* Image */

 

 

 

이제 카테고리가 보이도록하자. 

searchScreen의 내용을 가지고 온다. 

  const [sidebarIsOpen, setSidebarIsOpen] = useState(false)



  const productCategoryList = useSelector(state => state.productCategoryList)
  const {loading:loadingCategories, error:errorCategories, categories} = productCategoryList

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





...
          <aside className={sidebarIsOpen? 'open': ''}>
                <ul className="categories">
                  <li>
                    <strong>Categories</strong>
                    <button type="button" className="close-sidebar" onClick={()=>setSidebarIsOpen(false)}>
                        <i className="fa fa-close"></i>
                      </button>
                  </li>
                  {loadingCategories? <LoadingBox></LoadingBox> :
                    errorCategories? <MessageBox variant='danger'>{errorCategories}</MessageBox> :
                    (
                        <ul>
                            {categories.map(c=>
                                (
                                    <li key = {c}>
                                        <Link to ={`/search/category/${c}`} onClick={()=>setSidebarIsOpen(false)} >{c}</Link>
                                    </li>
                                ))
                            }
                        </ul>
                    )}
                </ul>
          </aside>

 

 

 

 

클릭하면 잘 이동한다. 

 

이제 상품 정렬순서기능과 골라내기 기능을 만들어보자. (sort&filter)

 

searchscreen에 템플릿을 잡아준다. 

<div className="col-1">
                    <div>
                        <h3>Department</h3>
                        {loadingCategories? <LoadingBox></LoadingBox> :
                        errorCategories? <MessageBox variant='danger'>{errorCategories}</MessageBox> :
                        (
                            <ul>
                                {categories.map(c=>
                                    (
                                        <li key = {c}>
                                            <Link to ={getFilterUrl({category:c})} className={c===category? 'active':''}>{c}</Link>
                                        </li>
                                    ))
                                }
                            </ul>
                        )}
                    </div>
                    <div>
                        <h3>Price</h3>
                        <ul>
                            {prices.map(p=>(
                                <li key={p.name}>
                                    <Link className={`${p.min}-${p.max}`=== `${min}-${max}`? 'active':''} to={getFilterUrl({min:p.min, max:p.max})} >{p.name}</Link>
                                </li>
                            ))}
                        </ul>
                    </div>
                </div>

 

frontend>utils.js를 만들어준다. 

 

export const prices = [{
    name:'Any',
    min:0,
    max:0
},{
    name:`$1 to $10`,
    min:1,
    max:10
},{
    name:`$10 to $100`,
    min:10,
    max:100
},{
    name:`$100 to $1000`,
    min:100,
    max:1000
}
]

export const ratings = [
    {
        name:'4starts & up',
        rating:4
    },
    {
        name:'3starts & up',
        rating:3
    },
    {
        name:'2starts & up',
        rating:2
    },
    {
        name:'1starts & up',
        rating:1
    },
]

 

다시 search screen와서 작성해준다. 

import { prices } from '../utils';

    const {name = 'all', category='all', min=0, max=0} = useParams();
    
    useEffect(() => {
        dispatch(listProducts({name:name!=='all'? name: '', category:category !=='all'? category:'', min, max}))
    }, [category, dispatch,name,min, max])

	const getFilterUrl = (filter) =>{
        const filterCategory = filter.category ||category;
        const filterName = filter.name ||name;
        const filterMin = filter.min? filter.min :filter.min===0? 0:min;
        const filterMax = filter.max? filter.max :filter.max===0? 0:max;
        return `/search/category/${filterCategory}/name/${filterName}/min/${filterMin}/max/${filterMax}`
    }



                    <div>
                        <h3>Price</h3>
                        <ul>
                            {prices.map(p=>(
                                <li key={p.name}>
                                    <Link className={`${p.min}-${p.max}`=== `${min}-${max}`? 'active':''} to={getFilterUrl({min:p.min, max:p.max})} >{p.name}</Link>
                                </li>
                            ))}
                        </ul>
                    </div>

app.js에 루트 추가

              <Route path="/search/category/:category/name/:name/min/:min/max/:max" component={SearchScreen} exact></Route>

 

 

 

이제 actions을 업데이트 해준다. 

(필터 추가)

여기서 max를 0으로 해놓는 이유는 라우터에서 0과 비교해놓을 것이기 때문이다. 

export const listProducts = ({seller='', name='', category = '', min=0, max=0}) =>async(dispatch)=>{
    dispatch({
        type:PRODUCT_LIST_REQUEST
    });
    try{
        const {data} = await Axios.get(`/api/products?seller=${seller}&name=${name}&category=${category}&min=${min}&max=${max}`);
        dispatch({type:PRODUCT_LIST_SUCCESS, payload:data});
    }catch(error){
        dispatch({type:PRODUCT_LIST_FAIL, payload:error.message});
    }
}

 

라우터도 수정하자 .

productRouter.get('/' ,expressAsyncHandler(async(req, res)=>{
    const seller = req.query.seller || ''
    const name = req.query.name || ''
    const category = req.query.category || ''
    const min = req.query.min && Number(req.query.min) !== 0? Number(req.query.min):0
    const max = req.query.max && Number(req.query.max) !== 0? Number(req.query.max):0

    const sellerFilter = seller? {seller} :{};
    const nameFilter = name? {name:{$regex:name, $options:'i'}} :{};
    const categoryFilter = category? {category} :{};
    const priceFilter = min && max ? {price: {$gte:min, $lte:max}}:{};
    const products = await Product.find({...sellerFilter, ...nameFilter, ...categoryFilter, ...priceFilter}).populate('seller', 'seller.name seller.logo');
    res.send(products);
}));

 

 

index.css

/* Search */
.search button{
  border-radius:0  0.5rem 0.5rem 0;
  border-right: none;
  margin-right: 0.5rem;
}
.search input{ 
  border-radius: 0.5rem 0 0 0.5rem;
  border-right: none;
  margin-left: 0.5rem;
}
.active{
  font-weight:bold;
}

 

 

잘 찾아진다. 

 

검색조건 중복적용도 잘 된다. 

 

이제 Rating으로 필터도 추가해보자. 

searchscreen의 템플릿

                    <div>
                        <h3>Price</h3>
                        <ul>
                            {prices.map(p=>(
                                <li key={p.name}>
                                    <Link className={`${p.min}-${p.max}`=== `${min}-${max}`? 'active':''} to={getFilterUrl({min:p.min, max:p.max})} >{p.name}</Link>
                                </li>
                            ))}
                        </ul>
                    </div>
                    <div>
                        <h3>Avg. Customer Reviews</h3>
                        <ul>
                            {ratings.map(r=>(
                                <li key={r.name}>
                                    <Link className={`${r.rating}`===`${rating}` 'active':''} to={getFilterUrl({rating:r.rating})} ><Rating></Rating></Link>
                                </li>
                            ))}
                        </ul>
                    </div>
                </div>

여기서 Rating은 component인데 여기서 조금 수정해보자. 

import React from 'react'

function Rating(props) {
    const {rating, numReviews,caption} =props;
    return (
        <div className="rating">
        <span><i className={ rating>=1? "fa fa-star": rating>=0.5?"fa fa-star-half-o":"fa fa-star-o" }></i></span>
        <span><i className={ rating>=2? "fa fa-star": rating>=1.5?"fa fa-star-half-o":"fa fa-star-o" }></i></span>
        <span><i className={ rating>=3? "fa fa-star": rating>=2.5?"fa fa-star-half-o":"fa fa-star-o" }></i></span>
        <span><i className={ rating>=4? "fa fa-star": rating>=3.5?"fa fa-star-half-o":"fa fa-star-o" }></i></span>
        <span><i className={ rating>=5? "fa fa-star": rating>=4.5?"fa fa-star-half-o":"fa fa-star-o" }></i></span>
        
        <span>{numReviews + ' reviews'}</span>
        {caption? <span>{caption}</span> : <span>{numReviews + 'reviews'}</span>}
    </div> 
    ) 
}

export default Rating

searchScreen.js

	const {name = 'all', category='all', min=0, max=0, rating=0} = useParams();
  
    useEffect(() => {
        dispatch(listProducts({name:name!=='all'? name: '', category:category !=='all'? category:'', min, max,rating}))
    }, [category, dispatch,name,min, max,rating])


    const getFilterUrl = (filter) =>{
        const filterCategory = filter.category ||category;
        const filterName = filter.name ||name;
        const filterMin = filter.min? filter.min :filter.min===0? 0:min;
        const filterMax = filter.max? filter.max :filter.max===0? 0:max;
        const filterRating = filter.rating||rating;
        return `/search/category/${filterCategory}/name/${filterName}/min/${filterMin}/max/${filterMax}/rating/${filterRating}`
    }
    
                        <div>
                        <h3>Avg. Customer Reviews</h3>
                        <ul>
                            {ratings.map(r=>(
                                <li key={r.name}>
                                    <Link className={`${r.rating}`===`${rating}`? 'active':''} to={getFilterUrl({rating:r.rating})} ><Rating caption={" & up"} rating={r.rating}></Rating></Link>
                                </li>
                            ))}
                        </ul>
                    </div>

 

product actions

 

export const listProducts = ({seller='', name='', category = '', min=0, max=0, rating=0}) =>async(dispatch)=>{
    dispatch({
        type:PRODUCT_LIST_REQUEST
    });
    try{
        const {data} = await Axios.get(`/api/products?seller=${seller}&name=${name}&category=${category}&min=${min}&max=${max}&rating=${rating}`);
        dispatch({type:PRODUCT_LIST_SUCCESS, payload:data});
    }catch(error){
        dispatch({type:PRODUCT_LIST_FAIL, payload:error.message});
    }
}

 

 

productRouter

productRouter.get('/' ,expressAsyncHandler(async(req, res)=>{
    const seller = req.query.seller || ''
    const name = req.query.name || ''
    const category = req.query.category || ''
    const min = req.query.min && Number(req.query.min) !== 0? Number(req.query.min):0
    const max = req.query.max && Number(req.query.max) !== 0? Number(req.query.max):0
    const rating = req.query.rating && Number(req.query.rating) !== 0? Number(req.query.rating):0

    const sellerFilter = seller? {seller} :{};
    const nameFilter = name? {name:{$regex:name, $options:'i'}} :{};
    const categoryFilter = category? {category} :{};
    const priceFilter = min && max ? {price: {$gte:min, $lte:max}}:{};
    const ratingFilter = rating ? {rating: {$gte:rating}}:{};
    const products = await Product.find({...sellerFilter, ...nameFilter, ...categoryFilter, ...priceFilter, ...ratingFilter}).populate('seller', 'seller.name seller.logo');
    res.send(products);
}));

app.js에 루트 추가(있던거에 rating만 추가)

 

              <Route path="/search/category/:category/name/:name/min/:min/max/:max/rating/:rating" component={SearchScreen} exact></Route>

 

 any카테고리를 넣자. 

searchScreen.js

 

                    <div>
                        <h3>Department</h3>
                        {loadingCategories? <LoadingBox></LoadingBox> :
                        errorCategories? <MessageBox variant='danger'>{errorCategories}</MessageBox> :
                        (
                            <ul>
                                <li>
                                    <Link to ={getFilterUrl({category:'all'})} className={'all'==='category'? 'active':''}>Any</Link>
                                </li>
                                {categories.map(c=>
                                    (
                                        <li key = {c}>
                                            <Link to ={getFilterUrl({category:c})} className={c===category? 'active':''}>{c}</Link>
                                        </li>
                                    ))
                                }
                            </ul>
                        )}
                    </div>

 

이제 sorting을 넣어보자. 

sorting은 selectbox가 될것이다. 

 

    const {name = 'all', category='all', min=0, max=0, rating=0, order='newest'} = useParams();
   useEffect(() => {
        dispatch(listProducts({name:name!=='all'? name: '', category:category !=='all'? category:'', min, max,rating,order}))
    }, [category, dispatch,name,min, max,rating,order])
    const getFilterUrl = (filter) =>{
        const filterCategory = filter.category ||category;
        const filterName = filter.name ||name;
        const filterMin = filter.min? filter.min :filter.min===0? 0:min;
        const filterMax = filter.max? filter.max :filter.max===0? 0:max;
        const filterRating = filter.rating||rating;
        const sortOrder = filter.order||order;
        return `/search/category/${filterCategory}/name/${filterName}/min/${filterMin}/max/${filterMax}/rating/${filterRating}/order/${sortOrder}`
    }
    
    
    
    
 			<div className="row">
                {loading? <LoadingBox></LoadingBox> :
                error? <MessageBox variant='danger'>{error}</MessageBox> :
                (<div>
                    {products.length} Results
                </div>
                )}
                <div>
                    Sort by {' '}
                    <select value = {order} onChange={e=>props.history.push(getFilterUrl({order:e.target.value}))}>
                        <option value="newest">Newest Arrivals</option>
                        <option value="lowest">Price: Low to High</option>
                        <option value="highest">Price: High to Low</option>
                        <option value="toprated">Avg. Customer Reviews</option>
                    </select>
                </div>
            </div>

 

productActions

export const listProducts = ({seller='', name='', category = '', min=0, max=0, rating=0,order=''}) =>async(dispatch)=>{
    dispatch({
        type:PRODUCT_LIST_REQUEST
    });
    try{
        const {data} = await Axios.get(`/api/products?seller=${seller}&name=${name}&category=${category}&min=${min}&max=${max}&rating=${rating}&order=${order}`);
        dispatch({type:PRODUCT_LIST_SUCCESS, payload:data});
    }catch(error){
        dispatch({type:PRODUCT_LIST_FAIL, payload:error.message});
    }
}

 

 

productRouter

productRouter.get('/' ,expressAsyncHandler(async(req, res)=>{
    const seller = req.query.seller || ''
    const name = req.query.name || ''
    const category = req.query.category || ''
    const min = req.query.min && Number(req.query.min) !== 0? Number(req.query.min):0
    const max = req.query.max && Number(req.query.max) !== 0? Number(req.query.max):0
    const rating = req.query.rating && Number(req.query.rating) !== 0? Number(req.query.rating):0
    const order = req.query.order || ''

    const sellerFilter = seller? {seller} :{};
    const nameFilter = name? {name:{$regex:name, $options:'i'}} :{};
    const categoryFilter = category? {category} :{};
    const priceFilter = min && max ? {price: {$gte:min, $lte:max}}:{};
    const ratingFilter = rating ? {rating: {$gte:rating}}:{};
    const sortOrder = order==='lowest'? {price:1} : 
                      order==='highest'? {price:-1} :
                      order==='toprated'? {rating:-1} :
                      {_id:-1};

    const products = await Product.find({...sellerFilter, ...nameFilter, ...categoryFilter, ...priceFilter, ...ratingFilter}).populate('seller', 'seller.name seller.logo').sort(sortOrder);
    res.send(products);
}));

app.js에 원래 있던거에 Order 추가

              <Route path="/search/category/:category/name/:name/min/:min/max/:max/rating/:rating/order/:order" component={SearchScreen} exact></Route>