본문 바로가기

E-commerce Vanila JS [5편] 랭킹, 상세페이지 제작

by Recstasy 2021. 12. 6.

라우팅으로 제품 상세페이지를 구현하려면 다음 3가지 조건이 필요하다.

 

1] 엔드포인트 :: 선택된 제품에 관한 데이터 경로

2] 데이터 :: 선택된 제품에 관한 데이터

3] 랜더링 :: 프론트에 전달되는 마크업 파일

4] 미들웨어 :: 라우트경로, DB경로, 랜더링 파일을 전달하는 중개자

5] 전송모듈 :: 데이터를 받고, 전달할 수 있는 모듈(ajax, axios, fetch, 기타...)

 

 대부분의 웹서비스는 결국 벡앤드와 프런트를 연결하고, 데이터를 주고받으며 페이지를 생성해내는 역할을 구현한다. 데이터, 디자인의 차이일 뿐, 내부의 구조는 거의 비슷하다. 해당 프로젝트에서 위의 역할을 담당하는 파일들을 정리하면 아래와 같다.

 

1] 엔드포인트 :: backend\server.js

2] 데이터 :: backend\data.js

3] 랜더링 ::  frontend\src\screens\

4] 미들웨어 :: api.js

5] 전송모듈 :: axios npm모듈

 

 

 

 


1 평점 시스템 구현

1) fontawesome 링크 연결하기

- root\frontend\index.html 상단에 폰트어썸 CDN 삽입

- https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css

 

 

2) 컴포넌트 폴더 생성

- root\frontend\src\conponents

 

 

3) Rating.js 컴포넌트 파일 생성

- root\frontend\src\components\Rating.js

 


 
const Rating = {
      render: async(props) => {
          if(!props.value){
              return `<div></div>`
          }
          return `
              <div class="rating">
 
                  <span>
                      <i class="${
                          props.value >= 1
                          ? 'fa fa-star'
                          : props.value >= 0.5
                          ? 'fa fa-star-half-o'
                          : 'fa fa-star-o'
                      }">
                      </i>
                  </span>
 
                  <span>
                      <i class="${
                          props.value >= 2
                          ? 'fa fa-star'
                          : props.value >= 1.5
                          ? 'fa fa-star-half-o'
                          : 'fa fa-star-o'
                      }">
                      </i>
                  </span>
 
                  <span>
                      <i class="${
                          props.value >= 3
                          ? 'fa fa-star'
                          : props.value >= 2.5
                          ? 'fa fa-star-half-o'
                          : 'fa fa-star-o'
                      }">
                      </i>
                  </span>
 
                  <span>
                      <i class="${
                          props.value >= 4
                          ? 'fa fa-star'
                          : props.value >= 3.5
                          ? 'fa fa-star-half-o'
                          : 'fa fa-star-o'
                      }">
                      </i>
                  </span>
 
                  <span>
                      <i class="${
                          props.value >= 5
                          ? 'fa fa-star'
                          : props.value >= 4.5
                          ? 'fa fa-star-half-o'
                          : 'fa fa-star-o'
                      }">
                      </i>
                  </span>
 
                  <span>${props.text || ''}</span>
              </div>
          `
      }
  }
 
  export default Rating;

[ components\Rating.js ]

 

 

4) HomeScreen.js 수정하기

- HomeScreen.js가 랜더링될 때, 평점 영역을 새롭게 추가한다. 

 


  import axios from 'axios';
  import Rating from '../components/Rating.js';
 
  const HomeScreen = {
      render: async() => {
          const response = await axios({
              url: "http://localhost:5000/api/products",
              headers:{
                  'Content-Type': 'application/json',
              },
          });
 
          if(!response || response.statusText !== 'OK'){ //데이터 응답 실패
              return '<div> Error in getting Data </div>'
          }
         
          const products = await response.data; //fetch Data 저장
          return `
              <ul class="products">
                  ${products.map(product =>`
                      <li>
                          <div class="product">
                              <a href="/#/product/${product._id}">
                                  <img src="${product.image}" alt="${product.name}">
                              </a>
                              <div class="product-name">
                                  <a href="/#/product/1">
                                      ${product.name}
                                  </a>
                              </div>
                      // 추가 시작...
                              <div class="product-rating">
                                  ${
                                      Rating.render({
                                          value: product.rating,
                                          text: `${product.numReviews} reviews`
                                      })
                                  }
                              </div>
                       // 추가 끝...
                              <div class="product-format">
                                  ${product.format}
                              </div>
                              <div class="product-price">
                                  $ ${ product.price}
                              </div>
                          </div>
                      </li>
                  ` ).join('\n')}
             
          `;
      },
 
  }
 
  export default HomeScreen;

[ root\frontend\src\screens\HomeScreen.js ]

 

 

5) style 추가하기

- 평점(별)관련 css부분 추가하기

 


  /* Rating */
    .rating {
        color: #ffc000;
        font-size: 1.8rem;
    }

    .rating span:last-child {
        color:#444444;
        font-size: 1.4rem;
    }

    .content{
        padding: 1rem;
    }

[ style.css ]

 

 

 

 

 

 


2 제품 EndPoint API추가

 

1) config.js 생성

- root\frontend\src\config.js

- config.js파일은 외부로 공개할 필요가 없는 정보를 관리한다.

 


    export
 const apiUrl = 'http://localhost:5000';

[ config.js ]

 

 

2) api.js 생성

- api.js는 중개자 역할을 한다. 사용자가 요청한 데이터를 직접적으로 받지 않고, 랜더링 파일의 요청에 따라 반환하는 방식으로 관련 데이터를 전달한다. 

 


  import {apiUrl} from './config.js';
  import axios from 'axios'
 
  export const getProduts = async(id) => {
      try{
          const response = await axios({
              url: `${apiUrl}/api/products/${id}`,
              method: 'GET',
              headers: {
                  'Content-Type': 'application/json',
              }
          });
 
          if(response.statusText !== 'OK'){
              console.log('메시지 서버 받기 실패')
              throw new Error(response.data.message);
          }
 
          return response.data;
 
      }catch(err){
          console.log(err);
          return { error: err.response.data.message || err.message}
      }
  }

[ root\frontend\src\api.js ]

 

 

3) ProductScreen.js 수정하기

- 일단, 엔드포인트의 데이터를 받아오는지 테스트할 목적으로 랜더링 파일을 구현한다.

 


 
import {getProduct} from '../api';
  import {parseRequestUrl} from '../utils';
  import Rating from '../components/Rating';
 
 
  const ProductScreen = {
      render: async() => {
          const request = parseRequestUrl();
          const product = await getProduct(request.id);
 
          if(product.error){
              return `<div> ${product.error} </div>`
          }
 
          return `<h1> ${product.name} </h1>`
      }
  }
 
  export default ProductScreen;

[ root\frontend\src\screens\ProductScreen.js ]

 

 

 

4) server.js 수정  

- root/backend/server.js

 

 
  import express from 'express';
  import cors from 'cors';
  import data from './data.js';
 
  const app = express();
 
  app.use(cors());
  app.get('/api/products', (req, res) => {
      res.send(data.products);
  });
 
  app.get('/api/products/:id', (req, res) => {
      const product = data.products.find((x) => x._id === req.params.id);
      if(product){
          res.send(product);
      }else{
          res.status(404).send({message: 'product Not Found!'});
      }
  })
 
  app.listen(5000, () => {
      console.log('server at http://localhost:5000');
  });

[ root\backend\server.js ]

 

 

 

 

 

 

3 제품 상세페이지 구현

 

1) ProductScreen.js 수정

- 메인페이지의 상품을 랜더링하는 상세페이지를 render()함수 반환값으로 프론트에 전달한다.

 


  import {getProduct} from '../api';
  import {parseRequestUrl} from '../utils';
  import Rating from '../components/Rating';
 
 
  const ProductScreen = {
     
      render: async() => {
          const request = parseRequestUrl();
          const product = await getProduct(request.id);
 
          if(product.error){
              return `<div> ${product.error} </div>`
          }
 
   // 수정 시작
          return `
              <div class="content">
                  <div class="back-to-result">
                      <a href="/#/"> Back to result </a>
                  </div>
                  <div class="details">
                      <div class="details-image">
                          <img src="${product.image}" alt="${product.name}"/>
                      </div>
                      <div class="details-info">
                          <ul>
                              <li>
                                  <h1>${product.name}</h1>
                              </li>
                              <li>
                                  ${Rating.render({
                                    value: product.rating,
                                    text: `${product.numReviews} reviews`,
                                  })}
                              </li>
                              <li>
                                  Price: <strong>${product.price}</strong>
                              <li>
                              <li>
                                  Description:
                                  <div>
                                      ${product.description}
                                  </div>
                              </li>
                          </ul>
                      </div>
                      <div class="details-action">
                          <ul>
                              <li>
                                  Price : ${product.price}
                              </li>
                              <li>
                                  Format :
                                  ${
                                      (product.format !== 'fbx')
                                      ? `<span class="success"> Other 3D Format </span>`
                                      : `<span class="error"> FBX </span>`
                                   }
                               </li>
                               <li>
                                   <button id="add-button" class="fw primary"> Add to Cart </button>
                              </li>
                          </ul>
                      </div>
                  </div>
              </div>
          `
     // 수정 끝
      }
  }
 
  export default ProductScreen;

[ root\frontend\src\screens\ProductScreen.js ]

 

 

2) style.css 수정

- 제품 상세페이지 관련 스타일 추가하기

 


 
/* Product Details */
    .details{
        display:flex;
        justify-content: space-between;
        align-items: flex-start;
        flex-wrap: wrap;
    }

    .details-image{
        flex: 2 1 60rem
    }

    .details-image img{
        max-width: 60rem;
        width: 100%;
    }

    .details-info,
    .details-action {
        flex: 1 1 30rem;
    }

    .details-info ul,
    .details-action ul{
        padding: 0;
        list-style-type: none;
    }

    .details-info h1{
        font-size: 2rem;
        margin: 0;
    }

    .details-info li,
    .details-action li{
        margin-bottom: 1rem;
    }

    .details-action{
        border: 0.1rem #808080 solid;
        border-radius: 0.5rem;
        background-color: #f8f8f8;
        padding: 1rem;
    }

[ root\frontend\style.css ]

 

 

 

 

 

 

 


4 기능 추가하기

1) ProductScreen.js 수정

- 장바구니 버튼에 대한 이벤트 기능을 임시로 구현한다. 

 


  
import {getProduct} from '../api';
  import {parseRequestUrl} from '../utils';
  import Rating from '../components/Rating';

  const ProductScreen = {
      after_render: () => {
  // 추가부분 <--
          const request = parseRequestUrl();
          document.getElementById('add-button').addEventListener('click', () => {
              document.location.hash = `/cart/${request.id}`
          });
   // 추가부분 -->
      },
      render: async() => {
          const request = parseRequestUrl();
    // 중략...

[ root\frontend\src\screens\ProductScreen.js ]

 

 

2) index.js 수정하기

- after_render()부분을 추가한다.

 


 
import {parseRequestUrl} from './utils.js';
  import HomeScreen from './screens/HomeScreen.js';
  import ProductScreen from './screens/ProductScreen.js';
  import Error404Screen from './screens/Error404Screen.js';
 
  const routes = {
      '/' : HomeScreen,
      '/product/:id': ProductScreen,
  }
 
  const router = async() => {
      const request = parseRequestUrl();
      const parseUrl = (request.resource ? `/${request.resource}` : '/') +
                       (request.id ? '/:id' : '' ) +
                       (request.verb ? `/${request.verb}` : '' );
 
      const screen = routes[parseUrl] ? routes[parseUrl] : Error404Screen;
 
      const mainContainer = document.getElementById('main-container');
      mainContainer.innerHTML = await screen.render();
      if(screen.after_render) await screen.after_render();
  }
 
  window.addEventListener('load', router);
  window.addEventListener('hashchange', router);

[ root\frontend\src\index.js ]

 

 

 

 

위의 코드까지 구현한 뒤에 터미널에서 서버(\root>)와 프론트 경로(root\frontend>)에서 npm start를 실행해본다.

 

* 실행 결과

상세 페이지

 

 

 

 

댓글

최신글 전체

이미지
제목
글쓴이
등록일