[팀프로젝트] Wegabox

minami·2022년 1월 30일
3

프로젝트

목록 보기
3/3

Wegabox

메가박스 사이트에 Wegabox 팀만의 너낌을 섞어 클론코딩을 하는 팀 프로젝트

  • 프로젝트 기간: 2022. 01. 10 - 2022. 01. 21 (약 2주)
  • 프로젝트 인원: 5명
    - 프론트엔드: 3명
    - 백엔드: 2명

1. 기획 단계

우리나라 영화 산업은 세계적으로도 생각보다 꽤나 큰 규모에 속한다. 인디 영화관들 외에 멀티플렉스를 자칭하는 대형 영화관도 CGV, 메가박스, 롯데시네마 3군데나 존재한다. 그중에서도 우리 팀은 메가박스 홈페이지를 선정했다. 대형 영화관 3군데 중에서 UI가 가장 깔끔한 편이었고, 예매가 꽤 복잡한 기능이어서 도전해볼 만 했다. 그리고 아무래도 두 번째 프로젝트라서 좀 더 이것저것 해보고 싶은 게 많았는데 기업협업 준비 등으로 프로젝트 외적으로 바빠질 것을 예상해서 주요기능을 축소하는 게 좋겠다는 조언을 듣고, 주요기능을 축소한 다음 주요기능을 완성하고 시간이 남으면 다른 기능들을 추가적으로 해보기로 했다.

주요기능을 축소하고, 다른 기능을 전부 추가기능으로 돌리면서 1차 프로젝트인 위해요와는 또 다른 의미에서 화면 구성이 달라지게 되어 이번에도 카카오 오븐을 이용해서 와이어프레임까지 한 번 제작해보았다.

주요 기능 정의

  1. 영화 목록 (메인 페이지 ➡ 목록 페이지)
  2. 카카오 소셜 로그인
  3. 영화 예매 기능 (영화 예매 페이지)

추가 기능 정의

  1. 메인 페이지
  2. 영화 목록 예매율순 정렬 (목록 페이지)
  3. 영화 목록 개봉작만 보기 (목록 페이지)
  4. 영화 상세 정보 (상세 페이지)
  5. 영화 평점, 예매율 등 데이터 시각화 (상세 페이지)

ERD

와이어프레임

위가박스 와이어프레임 보러 가기

2. 협업 방식

애자일 방법론의 SCRUM 방식

1차 프로젝트인 위해요 때 도입해본 방식을 그대로 이어서 해보기로 했다. 다들 1차 프로젝트를 거치면서 협업 툴과 스크럼 방식에도 익숙해져서 그런지 우왕좌왕 하지 않고 자연스럽게 할 수 있었던 것 같다. 그리고 데일리 스탠드업 미팅을 하기 전에 각자 미팅로그에 간단히 메모를 해두어서 스탠드업 미팅 시간에도 간결하게 필요한 부분에 대한 이야기만 할 수 있어서 좋았다.

또한 이번에는 1차 프로젝트 때와 git flow를 다르게 git merge가 아니라 git rebase를 사용했다. 처음에는 약간의 혼란이 있긴 했지만 프로젝트를 하는 동안 다들 잘 적응해서 큰 문제 없이 마무리할 수 있었다.

  • SPRINT
    • 단위: 1주
    • 미팅
      1. 플래닝 미팅 (스프린트 단위 시작)
      2. 데일리 스탠드업 미팅 (매일 아침)
      3. 회고 미팅 (스프린트 단위 끝)
    • 협업 툴
      • 트렐로
        • 팀 전체 / 프론트엔드 / 백엔드 라벨을 나누어 작업 단위 설정
        • 리스트
          1. Notice
          2. Backlog
          3. This Week
          4. In Progress
          5. Done
          6. Meetings
      • 슬랙
        • 커뮤니케이션 툴
      • git & github
        • 버전 관리 툴

3. 구현 결과

영화 예매 기능이 우리 팀의 주요 기능 중에 가장 어렵고 까다로운 기능이었기 때문에 원래 나는 목록 페이지 겸 메인 페이지를 최대한 빨리 끝내고 예매 기능을 담당한 팀원에게 붙어서 같이 할 예정이었다. 하지만 메인 페이지를 목록 페이지로 구현하려고 해보니 메가박스의 메인 페이지 UI 디자인을 살려서 목록 페이지에 적용하기엔 어려움이 따랐다. 그래서 팀원들과 상의하여 메인 페이지와 목록 페이지를 분리하기로 했고, 그 대신 메인 페이지를 최대한 간단하게 구현하기로 했다.

메인 페이지

메인 페이지에는 예매율 상위 4개의 영화 포스터를 보여주도록 구현했다. 각 영화 포스터의 hover 효과로는 해당 영화의 설명을 보여주도록 했고, 각 영화 포스터 아래의 예매 버튼을 누르면 해당 영화의 iduseNavigate()state를 활용해서 예매 페이지로 넘겨주어 해당 영화가 선택된 채로 예매 페이지가 나타나도록 구현해두었다. 또한, state를 활용해서 포스터를 클릭하면 해당 영화의 모든 정보를 state에 담아서 상세 페이지로 이동하도록 구현했다.

그리고 배경에는 예매율 1위 영화 포스터에 blur처리를 하고, 가상 선택자를 활용해서 가상 선택자의 배경에 검정색 투명도 조절 효과를 주어서 메가박스의 메인 페이지 UI 효과와 유사한 시각효과를 주었다.

목록 페이지

메인 페이지에서 박스오피스를 클릭하면 목록 페이지로 이동한다. 목록 페이지에서는 페이지네이션을 활용해서 예매율 내림차순으로 정렬된 영화를 한 번에 8개씩 끊어서 보여주도록 구현했다. 더보기 버튼을 누르면 그 다음 8개의 영화를 추가로 불러와서 화면에 보여주도록 했다.

영화 포스터 및 예매 버튼은 하나의 카드 컴포넌트로써 공통 컴포넌트로 빼두었다. 그리고 페이지에 따라 UI 스타일과 화면에 보여주는 정보를 컨트롤하기 위해서 useLocation()pathname을 활용했다. pathnameStyled-Componentsprops로 넘겨주면 메인 / 목록 / 상세 페이지에 따라 카드 컴포넌트의 내용과 스타일이 각각 다르게 표현된다. 그러기 위해서 굳이 커스텀 훅을 활용하지 않는 방법도 물론 있겠지만, 커스텀 훅을 연습해보기 위해서 usePageName()이라는 이름으로 커스텀 훅을 별도로 생성해두었다. 커스텀 훅은 사용할 땐 편한데 만들어서 적용할 땐 나를 넘 괴롭게 한다...🙃

개봉작만 보기 버튼은 목록 페이지의 페이지네이션까지 완성한 뒤에 더 할 것이 없을지 백엔드 팀원과 함께 고민하다가 만들기 어렵지 않을 것 같아서 추가한 기능이다. 스위치 버튼으로 UI를 만들기로 하고 기존 메가박스의 코드를 살펴보니 메가박스에서는 그냥 배경이미지를 사용해서 구현해두었더라. 하지만 나는 부드럽게 스위치가 전환되기를 바랐기 때문에 input의 체크박스와 label을 이용해서 직접 스위치 버튼을 만들었다! 생각보다 어렵지도 않았고, 내가 원한대로 스위치 버튼의 전환 효과도 부드럽게 구현이 잘 되어서 뿌듯했다.

2차 프로젝트를 할 때에는 라이브러리 사용이 자유로워져서 부드러운 스크롤을 위한 라이브러리도 사용했다. scroll-behavior: smooth를 사용해볼까 했는데 안 먹혀서 찾아보니 리액트에서는 이걸로 동작시킬 수 없는 것 같았다. 사실 딥하게 왜 그런지까지 찾아볼 시간은 없었기 때문에 일단 안되는 구나를 확인하자마자 라이브러리를 찾아서 적용했다😃

로딩 화면

서버에 요청한 데이터가 오기를 기다리는 동안 아무것도 안 보여줄 수는 없으므로 이번에도 로딩 화면으로 보여줄 공용 로딩 컴포넌트를 하나 만들었다. 그리고 데이터를 가져오는 커스텀 훅인 useFetch()를 사용해서 loadingtrue일 때 로딩 컴포넌트를 보여주도록 구현했다.

상세 페이지

원래 나까지 붙어서 하려고 했던 예매 기능을 맡은 팀원이 너무너무 잘해준데다가 한 기능을 같이 브랜치를 나눠서 한다는 게 좀 어려울 듯하여 상세 페이지는 추가 기능으로 구현하게 되었다. 처음 메가박스 홈페이지를 살펴볼 때에도 상세 페이지의 차트를 활용한 데이터 시각화가 인상 깊어서 한 번 해보고 싶었는데 다행히 해볼 수 있었다. 차트 라이브러리는 이전 인턴 때에 recharts.js를 사용해보았었는데, 이번에는 다른 차트 라이브러리르 사용해보고 싶어서 전 세계 개발자들이 가장 많이 쓰는 차트 라이브러리 중 하나인 chart.js를 사용해보았다. recharts.js와는 사용 방식이 좀 다르고, 커스텀 방식도 좀 달랐는데 둘 다 적응하면 사용하기 어렵지 않고 여러 종류의 차트를 보기에도 예쁘게 표현할 수 있어서 좋은 것 같다. 다만, 개인적으로는 기본 UI가 조금 더 깔끔하고 예쁜 쪽은 recharts.js 같다.

상세 페이지의 영화 포스터와 예매 버튼은 역시 위에서 설명했던 대로 공통 카드 컴포넌트와 pathname을 활용했다. 그리고 배경 이미지는 메인 페이지와 동일하게 blur와 가상 선택자를 활용한 검정 배경색을 활용하여 시각적인 효과를 꾸며주도록 했다.

그리고 영화 제목, 영화 포스터 이미지, 영화 상세 설명 같은 데이터는 모두 메인 페이지 구현 사항에서 언급했던대로 API를 사용한 게 아니라 useNavigate()state에 정보를 담아서 상세 페이지에 보내주는 방식으로 구현했다. react-router v6 이전에는 useHistory()에서 hitory.push()를 이용해서 데이터를 다른 페이지로 넘겨주었는데 react-router v6에서는 useNavigate()를 활용해야 한다. useHistory()deprecated되었기 때문! useNavigate()로 넘겨준 stateuseLocation()을 활용해서 다시 꺼내오면 된다. 그런데 이렇게 글로 줄줄이 써놓으면 처음 보는 사람들은 아마 감이 잘 안 올 수도 있으니 구현에 사용한 코드는 다음에서 소개하겠다.

4. 프로젝트 후기

😎 잘했다고 생각하는 점

1. 커스텀 훅(Custom Hooks)과 조금 더 친해진 점

useFetch와 usePageName

1차 프로젝트 때 이미 커스텀 훅을 맛보긴 했으나 이번에도 커스텀 훅을 만들어서 더 친해지는 시간을 가져보았다. Styled-Componenets와 함께 간편하게 카드 컴포넌트 스타일링을 하려고 만든 usePageName은 금방 만들어서 금방 적용했지만, useFetch 사용할 땐 커스텀 훅이랑 좀 싸우기도 했다. (그렇게 항상 조져지는 건 나다...) 커스텀 훅을 사용할 때 렌더링 순서가 어떻게 되는지 이해를 잘 하지 못해서 생긴 일이었는데, 그 덕에 콘솔 일일이 찍어가면서 렌더링 순서를 다시 한 번 공부하고 이해하는 계기가 되었다. 이게 바로 성장통이겠지. 그치만 이런 성장은...!!

아무튼 간단하게 만들고 적용까지 한 usePageName부터 살펴보자면, 우선 코드는 이렇게 되어 있다.

usePageName
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';

const usePageName = () => {
  const [pageName, setPageName] = useState('');
  const location = useLocation();

  useEffect(() => {
    const PATHNAME = location.pathname;
    if (PATHNAME === '/movies') setPageName('list');
    else if (PATHNAME === '/') setPageName('main');
    else setPageName('etc');
  }, [location]);

  return { pageName, location };
};

export default usePageName;

location이 달라질 때마다 PATHNAME을 가져와서 조건문에서 일치여부를 비교한 다음 pageName이라는 state를 바꿔주게 되어 있다. PATHNAME에 따라 설정된 pageName은 영화 카드 컴포넌트에서 아래와 같은 방식으로 사용된다.

const MovieButtonStyle = styled.button`
  width: 240px;
  height: 36px;
  line-height: 36px;
  font-size: 0.9rem;
  border-radius: 5px;
  color: ${({ theme }) => theme.whiteColor};
  background-color: ${({ theme, pageName }) => {
    switch (pageName) {
      case 'list':
        return theme.wegaboxPurple;
      default:
        return theme.buttonBlue;
    }
  }};
`;

사실 여기서 switch문을 사용하지 않고 삼항연산자 같은 걸 사용해도 되었는데 처음에는 case가 더 늘어날 것을 생각해서 일부러 사용했다. 아직 리팩토링을 다 진행하지 못한 상태여서 추후에는 삼항연산자를 사용해서 가독성을 높일 예정이다.

useFetch

커스텀훅 렌더링 순서와 페이지네이션 때문에 데이터 페치를 여러 번 해와야 하는 상황이 나를 힘들게 했던 useFetch... 형태도 여러 번 바꾸었고, 구글링도 많이 했다. 페이지네이션을 위해 offset이 변경될 때마다 변경된 URL을 활용해서 API를 호출해야 했는데 불러오는 데이터가 중복이 된다거나, 처음에 기본적으로 데이터를 한 번 불러와야 하는데 작동을 제대로 안 한다거나 하는 등 여러 가지 문제가 돌아가면서 한 번씩 발생했다. 그리고 그게 처음에는 내가 렌더링 순서를 잘 이해하지 못해서 벌어졌던 일이었는데, 나중에는 useFetch 안의 코드를 내가 잘못 짜서 문제가 되기도 했었다.

그렇게 한참 고민하고 공부하면서 완성된 useFetch의 모습은 아래와 같다.

import { useState } from 'react';

const useFetch = url => {
  const [loading, setLoading] = useState(false);
  const [result, setResult] = useState();

  const fetchData = () => {
    fetch(url)
      .then(response => response.json())
      .then(result => {
        setLoading(true);
        setResult(result);
      })
      .catch(e => {
        setLoading(false);
      })
      .finally(() => setLoading(false));
  };

  return [loading, result, fetchData];
};

export default useFetch;

처음에는 errorstate로 만들어뒀는데, 딱히 사용되지 않으니 빼도 좋을 것 같다는 멘토님의 조언으로 삭제했다. 그리고 처음에는 리턴되는 값을 객체 형식으로 묶어서 return {loading, result, fetchData}로 했었는데 리스트 형식으로 바꾸었다. 만약 한 페이지에서 useFetch를 활용한 API 호출이 처음 페이지가 렌더링 될 때 한 번만 하고 끝나는 게 아니라 여러 번 일어나게 해야 한다면 리스트 형식으로 해두어야 한다고 하는 글을 보았기 때문. 이 부분은 좀 더 공부를 해보아야 할 것 같다.

그리고 useFetch를 구글링하면 나오는 예제들처럼 fetchDatauseEffect에 넣지 않은 이유 역시 어차피 해당 커스텀 훅을 사용하려는 페이지에서 useEffect 안에 넣어 사용하기 때문에 중복할 필요가 없기 때문이다. 그래서 useFetch를 가장 많이 활용해야 했던 목록 페이지에서는 아래와 같이 사용했다.

  const [loading, result, fetchData] = useFetch(url);

  useEffect(() => {
    fetchData();
  }, [offset, isChecked]);

처음에는 useEffect 안의 코드가 정말 길었는데 머리를 쥐어 뜯는 리팩토링 결과 이렇게 깔끔하게 바뀔 수 있었다. 성장은 뽑힌 머리털에 비례하는 것 같다😃

2. chart.js 사용 및 차트 종류에 따른 객체를 활용한 조건부 렌더링

chart.js 사용을 위해 각 차트 및 styled-components의 공통 컴포넌트화 및 객체를 활용한 조건부 렌더링으로 가독성 UP하기

처음에는 2가지의 그래프만 넣으려고 했던 상세 페이지를 구현하다 보니 시간적 여유가 있기도 했고 개인적인 욕심도 나서 결국 4가지의 각각 다른 그래프를 모두 넣어버렸다. 그러다 보니 컴포넌트 분리를 하지 않고 한 파일 안에 일단 다 넣다보니 또 switch문을 활용해서 코드가 아주 길고 더러워졌다. 일단 구현을 다 하고 리팩토링은 나중에 하려고 하다 보니 벌어진 상황이었던지라 구현을 끝내고 코드 리뷰를 해주시던 멘토님을 깜짝 놀라게 해드렸지...

그래서 4가지나 되는 그래프를 각각 컴포넌트 분리를 하고, 해당 컴포넌트를 렌더링해주기 위해서 객체를 활용하여 코드를 아주 간단하게 줄였다. 객체를 활용한 조건부 렌더링은 이번에 처음 해보았는데 코드의 가독성 측면에서 정말 좋았던 것 같다.

리팩토링 전

정말이지 리팩토링 전은 너무 지저분해서 공개하기 민망하다...

리팩토링 후 - 객체를 활용한 조건부 렌더링 및 각 컴포넌트 분리
export default function MovieChart({ chart, ticketRate }) {
  const data = {
    ...
  };
  const charts = {
    radar: <RadarChart chart={chart} data={data} />,
    line: <LineChart chart={chart} data={data} />,
    bar: <BarChart chart={chart} data={data} ticketRate={ticketRate} />,
    polarArea: <PolarAreaChart chart={chart} data={data} />,
  };

  return charts[chart.type];
}

코드가 아주 그냥 깰꼼해졌쥬?

객체를 조건부 렌더링처럼 활용할 수 있다는 생각은 이전에 해보지 못했기 때문에 이미 알고 있었던 사람들은 겨우 에게? 할 수 있겠지만, 나에겐 신선한 시도였다. 렌더링의 조건이 여러 가지라면 객체를 활용할 수도 있다는 점, 앞으로도 꼭 기억해야겠다.

컴포넌트를 모두 분리해서 공통으로 사용되는 Styled-ComponetsStyles.js에 따로 모아두었다. 폴더 정리도 깔끔! 아주 맘에 들어!

3. 1차 프로젝트 때보다 더 많이 소통한 점

1차 프로젝트 때에는 팀원의 이탈과 코로나 확진이라는 큰 사건이 있었어서 오프라인 모임이 불가능한 기간이 있었다. 온라인으로라도 활발히 소통하고 싶었지만 줌 화상채팅 같은 걸 낯설어하는 팀원들이 많았어서 슬랙이나 개별 연락처를 활용할 수밖에 없었다. 그래서 서로 커뮤니케이션이 생각보다 잘 안 된 부분이 많았었다.

하지만 이번에는 그런 문제가 없기도 했고 최대한 블로커를 비롯한 문제가 있을 때엔 다같이 회의할 때 이야기해보려고 노력했다. 특히, 예매 기능처럼 복잡하고 어려운 기능은 구현을 어떤 식으로 해보아야 할지에 대해서도 다같이 머리를 맞대고 이야기하는 시간이 많았다. 그렇게 같이 고민하고 의견을 내다보니 예매 기능을 맡은 팀원도 새로운 아이디어나 힌트를 얻어서 해볼 수 있어서 좋았다는 말을 전해왔다. 역시 이게 바로 팀 프로젝트의 묘미이다.

4. 배포 완료!

아무래도 1차 프로젝트와 계속 비교를 할 수밖에 없어서 또 이야기를 해보자면, 1차 프로젝트 때에는 기능을 구현하는 것만 해도 아주 시간이 빠듯했어서 배포까지 할 정신도 없었다. 그래서 백엔드도 프론트엔드도 다 배포를 하지 못했으나, 이번에는 배포를 완료했다!

심지어 백엔드 팀원들이 아주 힘을 내주어서 프로젝트 기간 중에 이미 배포를 해주었다. 그래서 막바지에는 백엔드 팀원들이 굳이 서버를 로컬에서 열어주지 않아도 우리 프론트엔드 팀원들이 데이터를 가져오기가 아주 수월했다. 그러니 프론트엔드도 이번에는 꼭 배포를 하기로 마음을 먹었는데 마침 팀원 한 명이 배포를 해보고 싶다며 나서서 배포를 완료했다. 1차 프로젝트 팀원들도 모두 좋은 분들이었지만, 이번 2차 프로젝트 팀원들도 정말 좋다. 팀원 복이 좀 있나봐...

😥 아쉬웠던 점

1. 소셜 로그인 기능

소셜 로그인 기능을 이번에 처음 하다 보니 이걸 맡은 팀원이 아주 고생을 많이 했다. 중간중간 같은 팀원을 챙기려고 나름대로 노력하긴 했지만 조금 부족했던 탓인지 어려움이 많았다는 것을 잘 몰랐다. 본인이 말하지 않고 혼자서 어떻게든 해보려고 한 것 같은데 다른 팀원들에게 도움을 청했더라면 좋지 않았을까... 어쨌든 그래서 소셜 로그인 기능이 되긴 되는데 로그인 완료 후 다시 메인 화면으로 돌아오는 리다이렉트 설정이 제대로 되지 않는 문제가 있었다. 그게 아직도 해결되지 않은 상태여서 아무래도 기능적으로 모두 완전히 구현된 게 아니란 것이 아쉽다.

그래도 우리에겐 아직 시간이 있으니까 팀 프로젝트 최종 발표는 이미 끝났지만, 앞으로 보완하는 작업은 계속해서 할 것이다. 소셜 로그인도 꼭 완성해서 끝을 봐야지.

5. 프로젝트 소스

Wegabox 프론트엔드 레포지토리
Wegabox 백엔드 레포지토리
배포된 Wegabox 사이트

profile
함께 나아가는 개발자💪

0개의 댓글