[WeFrip] 2차 프로젝트 팀 회고

누리·2022년 12월 12일
0

🚀 My Second Team Project

저번 1차 팀프로젝트에 이어 두번째 프로젝트를 시작하게 되었다. 1차 프로젝트때 느꼈던 막연한 두려움 보다는 설레는 마음이 더컸고, 새로운 기능과 스택을 마주한다는 기대감이 컸던것 같다.
1차때보다 백엔드와도 소통을 적극적으로 했고, 프로젝트 결과물도 완성도가 높아진 결과물을 보니 조금은 성장한 것 같아 감회가 새롭다.
기업협업을 나가서는 어떤 성장의 기회가 나에게 찾아올지 기대가 된다.

프로젝트 기획

  • 팀이름 : WeFrip
  • WeFrip은 구매력이 높고 건강한 라이프 스타일을 추구하는 MZ세대들의 커뮤니티입니다.
  • 이전 세대와는 다르게 워라밸과 건강한 여가생활에 대한 관심이 높아짐에 따라 이러한 서비스의 필요성이 대두되었습니다.
  • WeFrip은 단순히 보여주는 것이 아닌 직접 찹여하고 경험하는 것입니다.

이 프로젝트는 프립을 참조하여 교육목적상으로 제작하였습니다.

개발기간 및 인원

개발기간 : 2022년 10월 31일(월) ~ 2022년 11월 10일(목)
개발인원 : 5명
1. 프론트엔드

  • 김온누리(본인) : 소셜로그인페이지, 메인페이지 카테고리, 리스트페이지, 호스트페이지(상품등록)
  • 이진혁 : 메인페이지, 상품디테일페이지, 호스트페이지(상품등록), 호스트페이지(상품조회)
  • 최규흠 : 마이페이지, 위시리스트페이지
  1. 백엔드
  • 박은송 : ERD구성, 소셜로그인 API, 마이페이지 API, 호스트페이지(상품등록, 상품조회)API
  • 신인혁 : ERD구성, 위시리스트페이지 API, 상품디테일페이지 API
  • 천송인(PM) : ERD구성, 메인페이지 API, 리스트페이지 API

기술 스택

  1. 프론트엔드 : React.js, styled Component
  2. 백엔드 : Node.js, MySql, Rest
  3. 공통 : Github, slack, trello, RESTful API

프로젝트 진행과정

매일 오후1시에 15분 정도 Daily Standing Meeting 진행하여 Trello를 작성하고 작업내용을 공유했다

  • Backlog - 전체 목표 티켓들
  • This Sprint - 이번 스프린트에 해야 할 티켓들
  • In Progress - 현재 개발중인 티켓들
  • In Review - PR 작성 후 리뷰중인 티켓들
  • Done - 완료된 티켓들
  • Fix - 나중에 개선할 사항이 있는 티켓들

카테고리를 4가지로 나눠서 구분했고, 회의록을 따로 작성하여 공통적으로 봐야할 부분들이 기록되어 있어 그때 그때 확인할 수 있도록 trello를 활용하였다.
전체 프로젝트 과정에서 진행 척도를 파악하기가 수월했다.

프로젝트 시연영상

WeFrip 보러가기

내가 구현한 영역

  1. 로그인페이지
    1차 프로젝트때 로그인페이지를 담당했었지만 소셜로그인 기능을 해보고 싶어서 이번에도 로그인 페이지를 자진해서 맡았다.
    카카오톡 Rest API를 활용하여 소셜 로그인 기능을 구현하는데 처음에 어떤 플로우로 로그인이 진행되는지 이해하기가 어려워 많은 문서들을 참조한 기억이 난다.
  • 카카오톡 소셜 로그인

    일단 이번 프로젝트에서는 fetch 메서드를 사용하지 않고 전체 프로젝트에서 팀원들과 axios를 사용하기로 정했기 때문에 axios 사용법을 익힐 수 있었다.
    useEffect(() => {
       const code = new URL(window.location.href).searchParams.get('code');
       try {
         axios
           .post(
             `https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&code=${code}`,
             {
               headers: {
                 'Content-type': 'application/x-www-form-urlencoded;charset=utf-8',
               },
             }
           )
           .then(res => res.data)
           .then(data => {
             if (data.access_token) {
               basicApi
                 .post(API.signin, { kakaoAccessToken: data.access_token })
                 .then(res => res.data)
                 .then(data => {
                   localStorage.setItem('token', data.accessToken);
                   navigate('/');
                 });
             } else {
               window.alert('로그인에 실패하였습니다.');
               navigate('/login');
             }
           });
       } catch (err) {
         alert(err);
       }
    로그인 페이지에서 카카오로그인 하기 버튼을 눌렀을때 링크 이동을
    카카오 auth url로 이동시켜주어 code의 searchParams로 값을 얻는다. (카카오로부터 받은 인가코드)

    그리고 인가코드를 카카오측에 보내주면 카카오는 REDIRECT_URI 일치여부등 유효성검증이 되면 토큰을 보내준다.

    받은 access_token을 백엔드 API로 보내주고 백엔드에서 access_token을 기반으로카카오 유저정보를 활용하여 우리사이트에서 사용하는 전용 토큰으로 다시 보내줄때 나는 그 토큰을 저장한다.
    처음이 이 플로우가 복잡하여 이해하기위해 몇번이고 계속 읽었다.

    이해가 안된다면 카카오 공식문서를 계속 읽어보는것을 권장한다.
  1. 메인페이지 네비게이션바
    해당 기능은 비교적 간단한 드롭다운 기능 구현이었는데, 사소한 문제하나가 나를 괴롭혔던 기억이 있어 기록으로 남겨보고자 한다.
  • 모달 드롭다운기능 구현

    메뉴 아이콘을 클릭하면 드롭다운 메뉴가 나타나고 드롭다운 메뉴가 나타난 상태에서 다시 메뉴 아이콘을 클릭하거나 드롭다운외의 영역을 클릭하면 드롭다운 메뉴가 사라져야했는데 click 이벤트로는 아예 드롭다운메뉴가 나타나지 않아 mousedown으로 처리했었다.

    const handleDropdown = e => {
       if (isOpen === true) {
         setIsOpen(false);
       } else {
         setIsOpen(true);
       }
     };
     useEffect(() => {
       const pageClickEvent = e => {
         if (
           dropdownRef.current !== null &&
           !dropdownRef.current.contains(e.target)
         ) {
           setIsOpen(!isOpen);
         }
       };
       if (isOpen) {
         window.addEventListener('mousedown', pageClickEvent);
       }
    
       return () => {
         window.removeEventListener('mousedown', pageClickEvent);
       };
     }, [isOpen]);

    그런데 문제는 mousedown으로 처리하게되면서 드롭다운이 활성화된 상태에서 다시 메뉴 아이콘을 '클릭' 했을때 드롭다운 메뉴가 사라지지 않는다는 것이다. 클릭이 아닌 마우스를 클릭하고 있는 상태 즉, mousedown일때에만 드롭다운메뉴가 사라지고 마우스를 떼는 순간 다시 드롭다운 메뉴가 나타나기 때문에 움직임이 매우 어색했다.

    문제해결은 시행착오에 걸린 시간에 비해 비교적 간단했다. 이벤트를 mousedown으로 할것이 아니라 click으로 바꾸고, e.stopPropagation()으로 이벤트 버블링을 막아주면 된다.

    const handleDropdown = e => {
       e.stopPropagation();
       if (isOpen === true) {
         setIsOpen(false);
       } else {
         setIsOpen(true);
       }
     };
     useEffect(() => {
       const pageClickEvent = e => {
         if (
           dropdownRef.current !== null &&
           !dropdownRef.current.contains(e.target)
         ) {
           setIsOpen(!isOpen);
         }
       };
       if (isOpen) {
         window.addEventListener('click', pageClickEvent);
       }
    
       return () => {
         window.removeEventListener('click', pageClickEvent);
       };
     }, [isOpen]);

    이벤트 버블링이란?
    이벤트가 연속하여 발생하는 버블 현상을 의미합니다. 클릭 시점에 해당 위치에서 이벤트가 발생하고 발생하고 다시 겹쳐진 요소를 올라가면서 해당 엘리먼트의 이벤트를 다시 발생시키는 현상을 의미한다. 이 경우 의도하지 않은 두 번째 이벤트가 추가로 발생하여 오류가 발생할 수 있기 때문에event.stopPropagation()을 사용하며 이 경우 이벤트 버블링은 Firing 하지 않아 이벤트가 발생되지 않는다

  1. 리스트 페이지
    리스트 페이지에서는 써보지 않았던 라이브러리들을 많이 사용해 보았다. 우선 날짜필터링을 구현하기 위해 react-datepicker 라이브러리를 커스텀해서 사용하였고 공통으로 react-modal 을 사용해 모달창으로 구현하였다.

    또 필터링 기능을 구현하기 위해 쿼리스트링을 사용하였다.
  • 필터링 기능구현
    useEffect(() => {
       if (subCategories) {
         basicApi
           .get(
             `${API.list}/${categories}/${subCategories}?sort=${
               sort ? sort : ''
             }&firstDate=${firstDateParams ? firstDateParams : ''}&lastDate=${
               lastDateParams ? lastDateParams : ''
             }`
           )
           .then(res => res.data)
           .then(data => {
             if (firstDateParams || sort) {
               setLists(data.data);
             } else {
               setLists(JSON.parse(data.data[0].products));
             }
           });
       } else {
         basicApi
           .get(
             `${API.list}/${categories}?sort=${sort ? sort : ''}&firstDate=${
               firstDateParams ? firstDateParams : ''
             }&lastDate=${lastDateParams ? lastDateParams : ''}`
           )
           .then(res => res.data)
           .then(data => {
             if (firstDateParams || sort) {
               setLists(data.data);
             } else {
               setTitle(JSON.parse(data.data[0].mainCategories)[0].korName);
               setSubCategoryLists(JSON.parse(data.data[0].subCategories));
               setLists(JSON.parse(data.data[0].products));
             }
           });
       }
     }, [categories, subCategories, lastDateParams, firstDateParams, sort]);
    페이지 내에 서브카테고리가 있고 page depth를 나눠서 쿼리스트링을 사용할 api를 요청하는 형식이라 식이 조금 길어진 것같다.
    if else 구문을 나누어서 보면 그리 어렵지만은 않게 보일 것이다.(물론 할때는 골치아팠지만)
  • 데이트 피커 커스텀
    const DateForm = ({ startDate, endDate, onChange }) => {
     return (
       <>
         <DatePicker
           locale={ko}
           selected={startDate}
           dateFormat="yyyy년MM월dd일(eee)"
           onChange={onChange}
           startDate={startDate}
           endDate={endDate}
           disabledKeyboardNavigation
           selectsRange
           monthsShown={2}
           minDate={new Date()}
           inline
           wrapperClassName="react-datepicker__header react-datepicker__day"
         />
         <DatePickerWrapperStyles />
       </>
     );
    };
    일단 DatePickerWrapperStyles라는 컴포넌트를 생성하고
    const DatePickerWrapperStyles = createGlobalStyle`
    .react-datepicker {
     border: none;
    }`
    createGlobalStyle 이라는 함수를 styled-components 에서 Import하여 만들어진 라이브러리 클래스명에 직접 스타일을 덮어서 전역에서 커스텀 해 줄 수 있다.
  1. 호스트페이지 판매 정보 탭영역
  • 폼데이터 활용
  1. 호스트페이지 판매 설명 탭영역
  • 폼데이터 사진업로드 기능

프로젝트를 진행하며 느낀점

  1. 열린 마음으로 새로운 기술 스택 받아들이기
    1차 프로젝트에서는 네트워크 서버 요청 메서드로 fetch, 스타일 언어로는 scss, github 명령어로 git merge를 사용했다면 이번 2차 프로젝트에서는 axios, styled-components, git rebase등 새로운 방법으로 많이 시도 하였다. 지금도 계속 계속 새로운 기술과 언어들이 쏟아져 나오고 있을 것이다. 개발자로서 도태되지 않고 살아남으려면 새로운 기술과 언어들을 배척하지 않고 열린마음으로 받아들일 수 있어야 한다고 생각한다. 물론 새로운 것만 고집하자는 것은 어리석은 생각이지만, 내가 모든 방법을 알고 있고 선택하는것과 옛날 방법만 알기 때문에 이전 방식을 사용하는것은 멀리 봤을때는 큰 차이가 나게될 것이다.

  2. 적당한 Deep Dive
    나는 이전까지 누군가에게 물어보거나 도움을 요청하지 않았었다. 최대한 나 혼자 어떻게든 해결하려고 했고 구글이 나의 멘토였다면, 이번 프로젝트에서는 멘토님이나 동기들에게 먼저 다가가 무엇이 문제인지 함께 고민해보고 해결해나가는 과정을 경험하면서 무조건 문제에 깊게 파고들고 혼자 해결해내는 것이 정답은 아니라는 것이라는 생각이 들었다. (내가 친 코드는 에러가 났을때 내눈에는 어디에 에러가 났는지 절대 안보인다)

🍺 마무리

첫 프로젝트와 사뭇 다른 2차 프로젝트였다. 사실 소통을 많이 하려고 노력했는데, 팀원들과 회고할때도 그 부분에 있어서 좋은 피드백을 받아서 뿌듯하다.

사실 새로운 기술 스택을 많이 사용하여서 또 새로운 것을 처음부터 배우는 느낌이라 많이 해맸고 모르는것을 구현해야해서 많이 힘들었지만, 같이 고민해주고 생각해주는 팀원들 덕분에 이겨낼 수 있었던 것 같다. 막상 내가 맡은 기능을 다 구현해내니 성취감이 들고 계속 새로운 기능들에 도전하고 싶은 마음이 든다.

다음 프로젝트때도 팀원들과 blocker 공유 하면서 같이 헤쳐나가는 프로젝트를 하고싶고, 새로운 기술들을 많이 접하고 싶다

profile
프론트엔드 개발자

0개의 댓글