1차 프로젝트 - Wesop✨ (상세페이지)

더미벨·2022년 7월 3일
0

약 2주간에 걸친 1차 프로젝트가 드디어 끝이 났다.

우리팀은 프론트 4명, 백엔드 1명으로 구성되어 'Aesop'이라는 사이트를 클론코딩했는데 내가 발표했던 사이트가 선정되어 그런지 더더욱 재미있게 작업할 수 있었다.
이번 1차 프로젝트에서 내가 맡았던 부분은 제품 상세페이지회원가입창이었는데, 이번 포스팅에서는 상세페이지에 대한 내용을 다루려고 한다.

상세페이지는 크게 네가지 영역으로 나누어 작업했다.

  1. 제일 상단의 제품이미지+제품소개 부분
  2. 이솝 내에서 제공되는 서비스 안내 문구 부분(이 부분은 하드코딩으로 진행했다.)
  3. 사용법
  4. 제품 추천 슬라이더

📍 제품소개


1) 체크박스 선택에 따라 달라지는 이미지와 가격 구현하기.

제품 소개 부분에서 가장 많은 시간을 소요했던 부분은 사이즈 선택에 따라 가격과 이미지가 변화하는 부분이었다. 원래는 각 사이즈별 state와 이미지, 가격에 대한 state를 따로 만들어 체크박스를 선택함에 따라 state값이 변경되도록 구현했었는데 조금 더 효율적인 코드를 고민하다가 아래와 같이 작성하게 되었다.

 const [detailItems, setDetailItems] = useState();
  const [selectedItem, setSelectedItem] = useState({});
  const params = useParams();

  useEffect(() => {
    fetch('http://10.58.4.206:8000/products/' + params.id)
      .then(response => response.json())
      .then(result => {
        setDetailItems(result);
        setSelectedItem({
          id: result.product.product_option[0].id,
          image_url: result.product.product_option[0].image_url,
          price:
            '₩ ' +
            parseInt(result.product.product_option[0].price)
              .toString()
              .replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ','), 
          sizes_mL: result.product.product_option[0].sizes_mL,
          is_include_pump: result.product.product_option[0].is_include_pump,
        });
      });
  }, []);

우선 선택된 아이템에 해당하는 데이터만 보이게 하는 selectedItem이라 state를 만들고, 값이 변경되어야 하는 image_url, price, size_mL 등을 state에 담아주었다.
이 때 백엔드에서 넘어오는 가격 데이터는 소수점단위까지 나오므로, parseInt를 이용해 소수점 아래 자리를 없애주고 정규표현식을 이용해 세자리마다 쉼표가 찍힐 수 있게 작성했다.

<div className="sizeBox">
              <p>사이즈</p>
              <div className="sizesFlex">
                {detailItems.product.product_option.map(
                  ({ id, sizes_mL, image_url, price, is_include_pump }) => {
                    return (
                      <label key={id}>
                        {detailItems.product.product_option.length === 1 ? (
                          <> </>
                        ) : (
                          <input
                            type="radio"
                            checked={selectedItem.id === id}
                            onClick={() =>
                              setSelectedItem({
                                id,
                                image_url,
                                price:
                                  '₩ ' +
                                  parseInt(price)
                                    .toString()
                                    .replace(
                                      /\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g,
                                      ','
                                    ),
                                is_include_pump,
                              })
                            }
                          />
                        )}
                        <div className="sizes">{sizes_mL}mL</div>
                      </label>
                    );
                  }
                )}
              </div>
            </div>
 			<button>카트에 추가하기 - {selectedItem.price} </button>

사이즈 선택 옵션이 아예 없거나, 뚜껑에 따라 체크박스가 늘어나는 경우도 있으므로 map함수를 사용하였다. 여기서 옵션이 하나일 경우(해당 배열의 길이가 1일 때)에는 공백이, 두개 이상일 경우에는 체크박스가 나오도록 삼항연산자를 이용해 작성하였다.

checkbox의 onClick 속성 안에 set함수를 실행시켜주고, checked에는 selectedItem.id === id를 넣어 클릭할 때마다 해당 조건식에 맞는 이미지와 가격이 화면에 보이도록 했다.

2) 반복되는 부분 map함수로 구현하기


소제목이 피부타입, 사용감, 주요성분으로 되어있는 각 부분이 똑같은 레이아웃으로 반복된다고 생각해 원래는 컴포넌트로 만든 후 각각에 해당하는 데이터를 입력해줬었다. 하지만 멘토님과 코드리뷰할 때, 너무 짧은 코드는 굳이 컴포넌트화 시키지 않아도 된다고 하셔서 컴포넌트는 지웠다.

 <div className="categories">
              {detailItems.product.features[0].feature.map(description => {
                return (
                  <div className="category" key={description.id}>
                    <p>{description.feature}</p>
                    <p className="element">{description.content}</p>
                  </div>
                );
              })}
            </div>

대신 다음과 같이 하나의 요소에 해당하는 부분만 코드를 작성하고, map함수를 이용해 반복실행해주었다.
나중에 보니 제품소개에 들어가는 카테고리들도 상품에 따라 피부타입에 대한 설명이 있기도 없기도, 나머지 카테고리들도 있는 것도 없는 것도 있었기 때문에 결론적으로는 위처럼 코드를 작성하는 것이 더 효율적이고 안전한 방법었던 것 같다!



📍 제공되는 서비스 안내


 <div className="serviceContainer">
        <div className="freeBox forBorder">
          <p className="topText">무료 선물 포장 서비스</p>
          <p>주문하신 모든 제품에 대해 선물 포장 서비스를 제공해 드립니다.</p>
        </div>
        <div className="freeBox">
          <p className="topText">샘플 & 코튼 백 증정</p>
          <p>
            모든 주문 건에 무료 샘플과 코튼 백을 제공해 드립니다. (샘플 종류는
            임의 지정이 불가합니다.)
          </p>
        </div>
      </div>

이 부분은 모든 제품 상세페이지에 동일하게 들어가는 내용이므로 하드코딩했다.



📍 사용법


<div className="howToUseContainer">
        <div className="itemImage">
          <img
            src={detailItems.product.additional_image_url}
            alt="제품이미지"
          />
        </div>
        <div className="howToUseBox">
          <div className="howToUseWrapper">
            <div className="howToUseTitle">
              {detailItems.product.additional_name}
            </div>
            <div className="howToUseText">
              {detailItems.product.additional_content}
            </div>
            <div className="categories">
              {detailItems.product.features[0].manual.map(feature => {
                return (
                  <div className="category" key={feature.id}>
                    <p>{feature.feature}</p>
                    <p className="element">{feature.content}</p>
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      </div>

제품소개 페이지에 나온 카테고리와 레이아웃이 똑같기 때문에 해당 코드의 데이터만 다르게 하여 역시 map함수를 돌려주었다.



📍 제품 추천 슬라이더(캐러쉘)


내가 공을 제일 많이 들인 부분이다..
1차 프로젝트에서는 최대한 많은 기능을 외부 라이브러리 없이 구현해보라고 하셔서 쌩으로 제젝한 캐러쉘이다...🥲

캐러쉘 제작 방법

⬆️해당 링크에 자세하게 기술해두었으니 쌩으로 캐러쉘을 제작하는 방법이 궁금하다면 참고 바랍니다..^^

profile
프론트엔드 개발자👩‍💻

0개의 댓글