Dr.Mozza + 프로젝트 (Dr.Jart 클론 프로젝트)

이종민·2021년 7월 17일
3

Project

목록 보기
1/2
post-thumbnail

1st Project "Dr.Mozzarella"

프로젝트 Github 링크 <= 클릭 시 'github' 로 이동

Dr.Mozza+ 사이트 시연영상<= 클릭 시 'Youtube' 로 이동

Frontend
이종민 - 회원가입, 로그인, 장바구니, 결제페이지
박정훈 - Nav 바, 메인페이지, 공용컴포넌트 (상품카드)
황소미 - 제품페이지, 제품 상세 페이지

Backend
이동명, 안희수, 김한준

프로젝트 기간
2021년 7월 5일 ~ 7월 16일 (2주)

기술스택
-React
-Javascript
-HTML
-CSS
-SASS
-Django
-Python

처음으로 제대로 팀을 갖춰 진행한 프로젝트로 Dr.Jart 미국 사이트를 기준으로 클론닝을하는 프로젝트였다.

사이트 기획의도

초기의 프로젝트 목표는 Dr.Jart 한국 사이트를 클론하는 것이 목표였으나 기획과정에서 Dr.Jart 미국사이트의 존재를 알게 되었고 미국사이트를 기본으로 기획의도를 변경하였다.

변경의 이유

  1. 심플하고 간결한 디자인
  • 미국 사이트와 한국 사이트를 직접 놓고 비교하면 미국 사이트 쪽이 굉장히 심플하고 가독성이 좋은 디자인을 가진 것을 느낄 수 있다. 이는 미국 쪽의 간결한 디자인이 팀이 완성하였을 때의 최종적인 결과물의 시각적 효과가 더 만족스러울 것 같았다.
  1. 애니메이션 효과
  • 현재의 팀원들의 실력 수준과 주어진 기간을 모두 고려하였을 때 한국 사이트는 많은 애니메이션 효과를 기반으로 사이트가 만들어졌기에 이를 구현하며 진행할 시 2주의 기간에 전체적인 기능을 하는 사이트로서의 기능이 완성되지 못할 가능성을 고려하여 버튼 정도에만 간결하게 애니메이션이 들어간 미국 사이트를 선정하게 되었다.

내 개인적인 개발에 대한 생각은 프로젝트 기간 내에 기능적인 면에서는 제대로 구동되야한다고 생각한다. 겉만 멀쩡하고 기능이 되지 않는다면 그건 장식품이나 마찬가지이다. 사이트는 목적에 맞춰서 기능이 최우선으로 구현되야한다고 생각한다.
그러기 위해서는 기간 내에 달성할 수 있는 목표와 업무배분이 중요하다. 그런 면에서 우리팀은 상당히 밸런스가 좋게 업무 분담이 되었다.

👍 프로젝트를 마친 느낌

내가 맡은 파트는 회원가입/로그인, 장바구니/결제페이지 이렇게 4가지를 맡았다. 2개씩 묶은 이유는 기능적인 면에서 연관되었기 때문이다. 회원가입을 하면 자연스럽게 로그인으로 넘어가게 된다. 장바구니에 상품이 담기면 자연스럽게 결제로 넘어가서 결제를 하는 것이 사이트 이용자가 사이트를 사용하게 되는 흐름이다. 따라서 비슷하게 연계되는 기능을 묶어서 맡아서 한다며 구상도 기능 구현면에서도 효율이 좋은 것 같다.

이제 코딩을 배운지 한달차라 프로젝트의 코드들이 현업에 종사하시는 분들이 볼때는 초보적인 코드일 수 있지만 사이트 하나를 기능구현하며 많은 고민을 통해 성장해 나간 것 같다. 흔히 게임으로 말하면 큰 퀘스트를 통해 경험치를 한번에 많이 얻은 느낌이 들었다.

사실 한편으로는 이번 첫 프로젝트 느낌이 군대에서 훈련소 끝났을 때의 느낌과도 비슷하다고 생각했다. 훈련소에서 5주 훈련이 끝나고 세상 다 가진 듯 뿌듯하고 좋았지만 시간지나 22개월의 군생활을 돌아보면 그 당시에 고작 튜토리얼을 끝내놓고 세상 다가진 듯 좋아한 것이나 마찬가지 였다. 지금도 첫 프로젝트를 마무리하고 나 자신에게 뿌듯해 하면 좋아했지만 한편으로는 아직 자대 배치도 못받은 신병처럼 아직 업계에 발도 못들인 초보자인데 너무 좋아하는 게 아닐까 하는 불안감이었다.

그래도 프로젝트를 진행하며 화기애애한 팀분위기 덕분에 큰 장애물을 만나지 않고 일사천리로 진행하였으며 원하는 목표만큼 구현을 하게 되어서 정말 기쁘다. 이 글로 다시 한번 팀원 모두에게 감사를 전하며 2차 프로젝트에서도 만나서 할 수 있다면 정말 행복할 것 같다.

이러한 소감들은 여기서 마무리하고 이번 프로젝트를 하며 내가 구현한 기능들에 대해 하나하나 설명해보겠다.

✅ 회원가입 페이지

회원가입에서 기본적으로 가지게 되는 정보는 이름, 이메일, 비밀번호, 주소 이렇게 4가지 정보를 사용자로 부터 받게된다.

회원가입 페이지는 크게 보면 input 4개와 button 1개로 이루어졌다고 볼 수 있다.

input 은 각 input 이 받는 정보에 맞는 '정규표현식' 을 통해 true & false 를 return 한다.

button 은 모든 input 에서 ture 를 받으면 fetch 함수를 POST 로 각 input 의 value 값을 보내며 가입완료 메시지와 함께 로그인 창으로 보낸다.

4개의 input 값 중 false 가 하나라도 있을 시 alert 을 통해서 사용자에게 입력정보를 다시 확인할 수 있게하였다.

이 페이지에서 쓸만한 코드라고 생각하는 것은

  handleInput = e => {
    const { name, value } = e.target;
    this.setState({
      [name]: value,
    });
  };

회원가입 페이지처럼 여러개의 input value 를 관리할 때 유용한 함수 이다. input 에 name 과 value 값을 주고 이를 통해 각 input 에 'onChange' 에 위의 함수를 넣어주게 되면 각 input value 를 원하는 state 에 값을 할당할 수 있다.

✅ 로그인 페이지

로그인 페이지는 기본적으로 회원가입 페이지와 기능적으로 비슷하다. 페이지를 크게 구분하면
이메일을 입력받는 input 과 비밀번호를 받는 input 2개로 구분되며 로그인 button 한개로 구분할 수 있다.

회원가입과 마찬가지로 각 input 은 각 input 이 받는 정보에 맞는 '정규표현식' 을 통해 true & false 를 return 한다.

button 역시 input 에서 ture 를 받으면 fetch 함수를 POST 로 각 input 의 value 값을 보내며 서버를 통해 받은 로그인 토큰을 로컬스토리지에 저장한다.

  checkAll = e => {
    const { email, password } = this.state;
    if (chkEmail(email) && chkPwd(password)) {
      fetch(LOGIN_API, {
        method: 'POST',
        headers: {
          'Content-type': 'application/json',
        },
        body: JSON.stringify({
          email: this.state.email,
          password: this.state.password,
        }),
      })
        .then(res => res.json())
        .then(res => {
          if (res.TOKEN) {
            localStorage.setItem('TOKEN', res.TOKEN);
            this.props.history.push('/main');
          } else {
            alert('입력하신 정보를 다시 확인해주세요.');
            this.setState({ email: '', password: '' });
          }
        });
    }
  };

회원가입 역시 위와 비슷한 함수를 사용한다. 위와 같은 방법으로 로그인이 성공하면 토큰이 로컬스토리지에 저장되여 앞으로의 기능에서 토큰을 확인할 때 로컬에서 토큰값을 받아와 확인할 수 있다.

✅ 장바구니 / 결제 페이지

개인적으로 장바구니 페이지를 코딩할 때 가장 재미있게 했다. 기능적인 면을 가장 많이 가지고 있어서 구현을 해나가는 재미가 있었다.

-장바구니 구현 기능 목록-
1. '+' / '-' 버튼으로 수량 증가/감소
2. 직접 수량을 입력 해서 수량 변경 가능
3. reduce 를 이용한 Subtotal 계산
4. 재고 이상 수량 입력 불가 및 alert 으로 재고부족 경고
5. 수량입력창max 99, min 1 이다.

위의 기능모두 구현 하였다.
우선 기본적으로 증가와 감소 함수 이다.

  handleIncrement = cart => {
    if (cart.quantity < cart.stocks) {
      const cartList = this.state.cartList.map(item => {
        if (item.option_id === cart.option_id) {
          const quantity = cart.quantity + 1;
          return { ...cart, quantity: quantity >= 99 ? 99 : quantity };
        }
        return item;
      });
      this.setState({ cartList });

      const requestOptions = {
        method: 'PATCH',
        headers: {
          Authorization: localStorage.getItem('TOKEN'),
        },
        body: JSON.stringify({
          quantity: cart.quantity >= 99 ? 99 : cart.quantity + 1,
        }),
      };
      fetch(`${CART_API}/${cart.option_id}`, requestOptions);
    } else {
      alert('재고가 부족합니다.');
    }
  };

증가함수의 경우 '+' 버튼 에 Onclick 으로 걸려있다. 우선 증가버튼을 클릭시 현재 수량서버에선 받은 재고 를 비교한다. 수량이 재고보다 작다면 수량을 하나 증가시킬 수 있다. 만약 수량이 재고를 넘어간다면 alert 으로 재고 부족을 사용자에게 알린다.

  handleDecrement = cart => {
    const cartList = this.state.cartList.map(item => {
      if (item.option_id === cart.option_id) {
        const quantity = cart.quantity - 1;
        return { ...cart, quantity: quantity <= 1 ? 1 : quantity };
      }
      return item;
    });
    this.setState({ cartList });

    const requestOptions = {
      method: 'PATCH',
      headers: {
        Authorization: localStorage.getItem('TOKEN'),
      },
      body: JSON.stringify({
        quantity: cart.quantity <= 1 ? 1 : cart.quantity - 1,
      }),
    };
    fetch(`${CART_API}/${cart.option_id}`, requestOptions);
  };

감소함수는 장바구니에는 재고가 1개 이상인 제품만 들어올 수 있고 수량은 1개 밑으로 내려갈 수 없으므로 재고를 확인하는 로직이 없다.

  quantityInput = (e, idx, cart) => {
    if (+e.target.value > cart.stocks) {
      alert('재고가 부족합니다.');
      return { ...cart, quantity: cart.stocks };
    } else if (+e.target.value > 99) {
      return { ...cart, quantity: 99 };
    } else if (+e.target.value === 0) {
      return { ...cart, quantity: 1 };
    }

    const nextCartList = this.state.cartList.map((cart, index) => {
      if (idx === index) {
        return { ...cart, quantity: +e.target.value };
      }
      return cart;
    });

    this.setState({ cartList: nextCartList });
    const requestOptions = {
      method: 'PATCH',
      headers: {
        Authorization: localStorage.getItem('TOKEN'),
      },
      body: JSON.stringify({ quantity: +e.target.value }),
    };
    fetch(`${CART_API}/${cart.option_id}`, requestOptions);
  };

이 부분은 직접 수량을 입력해서 변경하는 함수 이다. 장바구니의 구조는 Cart 부모컴포넌트Cartlist 라는 자식컴포넌트 를 가지고 있다. 수량을 변경하는 input은 자식컴포넌트가 가지고있다.
자식컴포넌트의 input 에서 입력된 value 를 부모컴포넌트의 quantity 라는 state 에 할당해주기 위해 e와 idx 라는 변수를 전달해주었다.

직접 입력하는 부분에서도 재고를 확인하며 입력 수량은 재고를 초과할 수 없으며 99개 이상의 값은 99로 고정된다. 마찬가지로 1이하의 값은 1로 고정된다.

  handleDelete = cart => {
    const cartList = this.state.cartList.filter(
      item => item.option_id !== cart.option_id
    );
    this.setState({ cartList });

    const requestOptions = {
      method: 'DELETE',
      headers: {
        Authorization: localStorage.getItem('TOKEN'),
      },
      body: JSON.stringify({ cartList }),
    };
    fetch(`${CART_API}/${cart.option_id}`, requestOptions);
  };

삭제 함수는 기본적으로 filter 를 사용하여 아이디 값 비교를 통한 삭제이다. 이와 같은 로직은 인스타그램 클론 프로젝트에서 댓글 삭제 기능에서 사용한 기능이다. 단순히 fetch 함수가 추가 된 것 뿐이다.

    const total = cartList
      .map(cart => cart.price * cart.quantity)
      .reduce((accumulator, currentValue) => accumulator + currentValue, 0);

위의 코드는 subtotal 을 구하는 함수 이다. map을 통해 각 리스트의 수량과 가격을 곱한 배열을 만들고 reduce를 통해 그 값을 합쳐주며 0을 초기값으로 설정해줬다.

✅ 결제 페이지

마지막으로 결제페이지이다.

-결제페이지 구현 기능 목록-
1. '%' 쿠폰 / '가격감소 쿠폰' 적용가능
2. 주소API 를 통한 주소입력

결제페이지의 오른쪽 'CART LIST' 부분은 쿠폰을 제외하면 장바구니와 완전히 동일하다.
reduce를 사용하여 subtotal을 계산한다.

쿠폰 부분을 설명하자면

  state = {
    coupon: '',
    discount_percent: 0,
    discount_price: 0,
  };
  sendCoupon = () => {
    const { coupon } = this.state;
    fetch(`${COUPON_API}/${coupon}`, {
      method: 'GET',
    })
      .then(res => res.json())
      .then(data => {
        this.setState({
          discount_price: data.results.discount_price,
          discount_percent: data.results.discount_percent,
        });
      });
  };

{formatToUSD(total * (1 - discount_percent) - discount_price)}

위와 같이 input 창에 적힌 value 를 서버로 보내면 서버에서 쿠폰 정보를 받아오고 해당 쿠폰 정보에 따라 % 쿠폰의 경우 discount_percent 를 가격감소 쿠폰의 경우 discount_price 에 값을 할당한다. 두 값은 기본적으로 0의 값을 가지고 있다. 값이 설정되면 아래 total 에 할당 값이 들어가 가격을 감소하는 방법이다.

0개의 댓글