토이프로젝트 | 월드컵

배포된 월드컵 사이트 보러가기

시작하게 된 계기

월드컵 프로젝트는 유튜브 침착맨님의 월드컵 컨텐츠를 보고 영감을 받아 제작하였다.
월드컵 페이지를 제작한다면 사용자가 자바스크립트 메서드 월드컵을 참여해서 사용자의 통계를 내고 개발을 처음 배우는 분들이 통계를 참고하여 많이 쓰이는 메서드에 대해서 공부하면 어떨까 하는 생각에 만들어 보게 되었다.

또한 위코드 부트캠프와 원티드 프리 온보딩 과정을 수료하면서 코딩에 대한 자신감에 생긴 나는 무엇이든 만들 수 있을 것 같다는 자신감과 함께 뽐내고 싶었다.

원티드 프리 온보딩 과정에서 팀원 이셨던 프론트엔드 개발자 영길님 에게 토이프로젝트에 대한 구성을 설명 드렸고 영길님이 같이 하시길 원하셔서 프론트엔드 두 명에서 개발을 하게 되었다.

프로젝트 소개

월드컵 프로젝트 소개

월드컵 사이트는 하나의 주제에 16개의 항목이 존재하고 16강부터 결승까지 두 개의 항목을 나열한 뒤 사용자가 선택한 요소가 상위 라운드로 진출하는 시스템입니다.

월드컵 참여를 완료하거나 랭킹 보기를 눌렀을 때 사용자들이 선택한 요소들을 집계한 통계 페이지 이동합니다. 각 항목의 우승 비율, 라운드 승률을 오름차순, 내림차순으로 정렬이 가능하며 댓글 창을 통해 해당 통계에 대한 사용자들의 생각을 볼 수도 있습니다.

로그인 기능도 구현되어 있으며 로그인된 유저일 시 댓글 입력 , 월드컵 생성 , 마이페이지에서 자신이 생성한 월드컵 삭제 등의 기능 또한 제공됩니다.

모바일 사용자들 또한 있을 거라 생각하여 미디어 쿼리를 사용한 모바일 화면도 구현했습니다.

프로젝트 기간

2022.03.09 - 2022.03.16

프로젝트 팀원

프론트엔드 개발자 2명

사용한 기술 스택

React , TypeScript , styled-component , Json-Server , Axios , GitHub

초기 기획 단계

Json-Server를 사용하여 REST-API 구축

  • 서버를 활용하여 유저들의 월드컵 참여로 쌓이는 데이터로 유의미한 통계를 내고, 댓글을 통해 사용자들의 의견을 확인할 수 있는 것을 목표로 했습니다.
  • 서버는 필요하지만 백엔드 개발자가 없어서 Json-Server를 통해 간단하게 REST-API 구축.
  • Json-Server의 API를 Axios를 이용하여 REST-API 방식으로 통신했습니다.

월드컵 프로젝트에 필요한 API 설계

[
  {
    "title": 월드컵 이름,
    "creatorId": 생성자 아이디,
    "id": 월드컵 아이디,
    "count": 월드컵 진행 횟수,
    "createdAt": 월드컵 생성 시간,
    "list": [
      {
        "id": 월드컵 요소 아이디,
        "candidate": 월드컵 요소 이름,
        "roundWin": 라운드 승리 횟수,
        "roundLose": 라운드 패배 횟수,
        "champion": 우승 횟수
      },
      . 
      . 
      .
      ],
    "comments": [
      {
        "id": 댓글의 아이디,
        "text": 댓글 내용,
        "createdAt": 댓글 작성 시간,
        "creatorId": 댓글 작성자 아이디,
        "userId": 작성자 이름
      },
      .
      .
      .
    ],
  },
]

피그마를 사용한 UI 초기 설계


단순하게 레이아웃만 설계하고 시작한 이유는 레이아웃으로 보여주는 방식에 따라 구현 해야 하는 로직이 달라질 수 있다고 생각되어 현업에서 많이 쓰이는 피그마를 사용하여 세세하게 디자인적인 요소는 제외하고 대강의 틀을 잡아놓고 시작하게 되었습니다.

모바일 화면은 초기 단계에서 설계하지 않고 먼저 데스크톱 사이트를 기본으로 구현하고 사용자에게 적합한 모바일 화면을 미디어 쿼리를 사용하여 구현했습니다.


트렐로를 사용한 역할 분담

개발 진행 상황 공유를 위해 트렐로를 이용하였습니다. 트렐로를 사용함으로써 원만한 소통을 통해 협업을 진행했습니다.


페이지 별 로직

월드컵 페이지

라운드 별 두 개의 항목 배치

  • index state값을 생성하여 화면에 보여지는 두 가지 항목은 해당 월드컵 list의 [index] , [index+1] 번째의 요소를 보여줍니다. 두 가지 항목 중 하나가 선택됐을 때 index 값은 +2가 되며 다음의 요소들을 보여줍니다.

선택 애니메이션

  • 두 가지의 항목 중 선택되는 동작에 따라 left , right , default 의 state값이 할당 됩니다.
  • 할당된 state 값에 따라 style-component의 속성이 바뀌는 식으로 구현했습니다.
  • CSS 속성인 transition , transform 을 사용하여 부드럽게 가운데로 위치하도록 구현했습니다.

대진표 로직

  • 두 개의 항목 중 하나를 클릭했을 때 해당 요소를 대진표 list 에 저장합니다.
  • 대진표 state 는 총 8강 4강 결승 이렇게 구성되어 있으며, 8강의 리스트 8개가 채워진다면 화면에 보여질 두개의 항목은 8강 의 대진표를 참조하게 되고 그 이후부터 선택되는 항목은 4강의 리스트에 추가되는 형식으로 진행되게 로직을 구현했습니다.

월드컵 입장 시 리스트 배열순서 랜덤화

  • 좀 더 정확한 통계를 위하여 배열의 순서를 섞어주는 로직 구현.
    const shuffleArray = (array: IWorldCupItemProps[]) => {
        for (let i = 0; i < array.length; i++) {
          let j = Math.floor(Math.random() * (i + 1));
          [array[i], array[j]] = [array[j], array[i]];
        }
        return array;
      };

진행된 월드컵의 데이터를 기존의 데이터에 추가

  • 사용자가 중간에 월드컵을 진행하다가 페이지에서 이탈 시 에는 데이터에 영향이 가지 않도록 useEffect의 의존성 배열에 우승자 state를 할당하여 우승자의 state값이 변경되었을 때 변경된 데이터를 Axios의 put 메서드를 사용하여 기존의 데이터에 변경할 수 있도록 하였습니다.

월드컵 우승자 모달

우승자 모달 컴포넌트 및 랭킹 페이지로 라우팅

  • 결승전을 치르고 우승자가 탄생하면 모달의 형태로 우승자를 표시하는 모달창이 등장합니다.
  • useNavigate를 사용하여 '랭킹 보러 가기' 텍스트를 클릭 시 해당 월드컵에 해당하는 랭킹 페이지로 이동하게 됩니다.

랭킹 페이지

월드컵 통계

  • 월드컵의 통계는 사용자가 월드컵에 참여했다면 기존의 데이터에 참여한 월드컵의 내용이 적용된 통계 데이터를 보여주고 , 사용자가 월드컵에 참여하지 않고 '랭킹보기' 를 통해 보게 된다면 기존의 통계 데이터를 보여줍니다
  • 월드컵에는 전체 진행된 경기 수 그리고 각 항목은 라운드 승 , 라운드 패 , 우승한 횟수 등의 데이터를 갖고 있습니다
  • 우승 비율은 각 항목이 갖고 있는 우승 횟수 / 해당 월드컵의 전체 경기 수를 퍼센트로 나타냅니다.
  • 라운드 승률은 각 항목의 roundWin / (roundWin + roundLose) 값을 퍼센트로 나타냅니다.
  • 우승 비율라운드 승률 각 항목의 퍼센트를 차트 형식으로 데이터를 시각화 해봤습니다.
  • 차트에 있는 파란색 막대기의 width값은 각 항목의 퍼센트 값을 참조합니다.

통계 정렬 방식

  • 통계의 정렬 방식에는 이름순, 우승비율 순, 라운드 승률 순, 세 가지가 존재하며 모두 오름차순 내림차순이 존재합니다.
  • 통계 페이지로 이동 시 기본 정렬값은 우승비율 순 의 내림차순으로 정렬됩니다.
  • 상단의 정렬 타입을 클릭 하면 각각의 state값을 가지고 있어 클릭한 정렬의 타입만 배경색이 칠해지며 우측에 오름차순 , 내림차순을 표시하는 화살표가 표시됩니다.
  • 기본적으로 sort메서드의 매개변수에서 오름차순과 내림차순에 따라 첫 번째 매개변숫값과 두 번째 매개변수 값을 빼는 순서를 다르게 했습니다.
  • 우승비율같은 경우에는 좀 더 보기 좋은 통계를 위해 라운드 승률로 먼저 정렬을 한 데이터를 우승 비율 순으로 정렬하게 했습니다. 라운드 승률 또한 우승 비율로 먼저 정렬된 데이터로 라운드 승률을 정렬시켰습니다.

월드컵이 생성되기만 하고 진행된 적이 없을 때

noData ? (
        <NoData>😢 진행된 적 없는 월드컵입니다.</NoData>
      ) : (
        <>
          <Ranking />
          <Wrapper>
            <CommentWrapper>
              <CommentForm userObj={userObj} />
            </CommentWrapper>
          </Wrapper>
        </>
      )}
  • 통계 데이터가 없다면 기존의 화면이 아닌 '😢 진행된 적 없는 월드컵입니다.' 라는 텍스트를 출력하도록 삼항연산자를 활용하여 예외 처리 했습니다.

댓글 컴포넌트

Axios의 get,put을 활용한 해당 월드컵 댓글 데이터 관리

  • axios의 get, put 메서드를 이용해 get으로는 월드컵의 아이디에 해당되는 데이터를 불러와 전체 데이터와 댓글 데이터를 얻을 수 있었고 put으로는 불러온 전체 데이터와 댓글 데이터를 서버에 보낼 객체에 이용하여 월드컵 데이터 안의 comment 배열 데이터만 수정하였습니다.

댓글의 작성된 시간을 사용자들에게 표시

  • 댓글이 작성된 시간을 Now,몇 분 전, 몇 시간 전 , 몇 월 며칠 그리고 연도가 바뀌었을 경우 연도가 표시되도록 하였습니다.

댓글 삭제 로직

  const onDeleteClick = async (id: string) => {
    const ok = window.confirm('정말 삭제하시겠습니까?');
    if (ok) {
      const findIndex = data.comments.findIndex(
        (item: IWorldCupCommentProps) => item.id === id
      );
      const newComments = [
        ...data.comments.slice(0, findIndex),
        ...data.comments.slice(findIndex + 1),
      ];
      await axios
        .put(`${BASE_URL}/world/${keyword[2]}`, {
          ...data,
          comments: newComments,
        })
        .then(() => setRefetch((prev) => !prev));
    }
  };
  • confirm 기능을 이용해 사용자가 실수로 삭제하지 않도록 하였습니다.
  • findIndex로 해당 월드컵의 댓글 데이터와 매개변수로 받은 id를 비교해 몇 번째에 위치했는지 알아내고 slice를 이용해 해당하는 댓글을 삭제했습니다.
  • setRefetch를 통해 삭제되고 상태 값을 변하게 하여 페이지의 리 렌더를 유도하여 실시간으로 삭제된 데이터를 불러오도록 하였습니다.

로그인 여부에 따라 다르게 표시

  • 비로그인 시 로그인하여 댓글을 입력해주세요라고 표시하며 입력되지 않습니다.

Home 페이지

생성된 월드컵 데이터 뿌리기 및 타입별 정렬

  • axios의 get 메서드를 통해 서버로부터 생성된 월드컵을 받아오고 map 메서드를 통해 화면에 뿌렸습니다.
  • sort메서드를 통해 인기순 , 최신순 , 이름순으로 정렬했습니다. 처음 Home 페이지로 이동했을 때 인기순의 내림차순을 기본 정렬 타입으로 지정했습니다.

월드컵 생성 페이지

Axios의 post를 활용한 서버 데이터 추가

  • axiospost 메서드를 이용해 월드컵 사이트에 필요한 API 데이터들을 객체 형식으로 Server DB에 보냈습니다.

React Hook Form 을 이용한 불필요한 코드 관리

  • 16개의 인풋 창과 입력된 값들을 관리하기 위해 React Hook Form을 사용하여 사용하는 코드량을 줄였습니다.

uuidv4 를 활용한 유니크한 아이디 값 추가

  • uuidv4를 사용하여 각각 다른 id를 가지게 하여 API에 사용할 시 중복되지 않게 설계하였습니다.

에러 메세지 표시

  • 필수 입력값, 최대 글자 수를 넘었을 때 에러 메시지가 각 입력창 아래에 표시되도록 하였습니다.

마이 페이지

자신이 만든 월드컵 데이터 표시

  • axios의 get 메서드를 이용해 로그인된 아이디가 월드컵 생성자 아이디인 경우만 데이터를 불러왔습니다.

데이터의 여부에 따라 다르게 표시

  • 불러온 데이터의 length 값이 0일 경우 생성한 월드컵이 없다고 표시했습니다.
  • 불러온 데이터가 존재할 경우 월드컵 리스트 컴포넌트를 이용해 화면에 표시했습니다.

조건문을 활용한 월드컵 리스트 컴포넌트 재사용

  • 월드컵 리스트는 홈과 마이 페이지에서 재사용 되는 컴포넌트입니다.
    마이 페이지에서는 삭제하기 기능을 지원하기 때문에 월드컵 리스트 컴포넌트에 props로 setData 가 있을 경우 삭제 기능이 화면에 표시되도록 하였고 함수에서도 setData가 없을 경우 return; 을 해주어 오류를 방지했습니다.

로그인

Google Cloude Platform

  • Google Cloude Platform 에서 사용자 인증 정보를 생성해 OAuth 2.0 클라이언트 ID를 생성해 프로젝트에 이용했습니다.
  • 배포한 사이트의 URL에서만 클라이언트 ID가 이용 가능하도록 하여 사이트의 보안을 설정했습니다.

react-google-login 을 활용한 구글 로그인 구현

  • react-google-login 라이브러리를 활용해 사용자에게 구글 로그인을 지원하고 사용자의 정보를 이용할 수 있게 사용자 정보를 state 값으로 저장했습니다.

프로젝트를 진행하며 추가, 수정된 로직들

월드컵을 진행할 배열의 순서 랜덤화 추가

월드컵이 진행될 때 만약 랜덤으로 배열의 순서를 배치하지 않는다면 제대로 된 통계가 나올 수 없는 문제점이 있었습니다.

ex) 월드컵 리스트 전체에서 원래대로라면 1등을 할 A 항목과 2등을 할 B 항목이 있다. 하지만 이 항목은 월드컵 리스트 1 , 2번째에 고정으로 배치되어 있다면 B 항목은 A 항목에 패배하는 경우가 많아 원래라면 2등이지만 상위권에 위치하는 것은 불가능할 것이다.

하지만 배열의 항목들의 순서를 random 메서드를 사용하여 섞어 준다면 예와 같은 상황이 계속 유지되지 않기 때문에 제대로 된 통계가 나오게 됩니다.


통계를 정렬하는 로직 수정

월드컵의 우승 비율 통계를 각 요소의 우승 횟수를 기준으로만 정렬한다면 통계가 보기 좋게 나오지 않는 문제점이 있었습니다.

ex) 우승 비율이 같은 항목이 있다면 해당 항목 안에서 순위를 정할 때 라운드 승률이 높은 항목이 상위권에 위치 하는것이 맞지만 우승 횟수만을 기준으로 불가능하다

해결책으로 우승 비율을 정렬한다면 라운드 승률을 기준으로 먼저 정렬을 한 데이터를 우승 비율로 다시 한번 정렬 하는 방법을 사용했습니다.
라운드 승률 또한 우승 비율을 먼저 정렬 후 라운드 승률을 기준으로 정렬했습니다.


모바일 화면에서의 헤더 부분 수정

모바일 화면에서 헤더 부분의 홈 버튼, 월드컵 생성 , 나의 월드컵 , 로그인 탭을 모두 담기에는 화면의 길이가 부족한 문제가 있었습니다.

해당 문제는 미디어 쿼리를 사용해서 월드컵 만들기 메뉴바 안에 홈 , 월드컵 생성 , 나의 월드컵 탭을 포함하는 방식으로 해결했습니다. 메뉴바는 좌측에서 우측으로 나오는 모달 형식이며 바깥 부분을 누르면 메뉴바가 닫히도록 구현했습니다.


데이터를 받아오는 동안 출력되는 로딩 스피너 컴포넌트 추가

Mock-up 데이터를 사용하여 개발 할 때에는 몰랐지만 json-server를 통해 서버로부터 데이터를 받아오도록 구현 했을 때 서버로 부터 데이터를 받아오는데 시간이 걸려 몇몇의 페이지에서 데이터가 뿌려지지 않은 화면이 출력되는 문제가 있었습니다.

해당 문제는 로딩 스피너 컴포넌트를 따로 제작하여 각 컴포넌트마다 isLoading state 값을 생성하여 데이터를 받아오는 useEffect 함수 첫 부분에서 isLoading state 값에 true를 할당하여 로딩 중, 그리고 데이터를 다 받아온 시점에서 isLoading state 값에 false를 할당하여 로딩 완료를 상태 값으로 지정하여 isLoading state 값이 true 일 때만 삼항 연산자를 통해 로딩 컴포넌트를 화면에 출력하도록 했습니다.

로딩 스피너는 css의 keyframsesanimation을 사용하여 구현했으며 코드는 이러합니다.

const ModalContent = styled.div<`
  position: absolute;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
`;
const rotate360 = keyframes`
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
`;
const Spinner = styled.div`
  animation: ${rotate360} 0.5s linear infinite;
  transform: translateZ(0);
  border-top: 4px solid white;
  border-right: 4px solid white;
  border-bottom: 4px solid #424874;
  border-left: 4px solid #424874;
  background: transparent;
  width: 40px;
  height: 40px;
  border-radius: 50%;
`;

프로젝트를 진행하며 맞닥뜨린 어려움들

Axios의 put 메서드를 사용한 데이터 변경

월드컵이 모두 진행된 후 put 메서드를 통해 월드컵 참여를 통해 기존의 월드컵 데이터에서 추가로 적용된 데이터를 바꾸는 데에 있어서 많이 해맸었습니다...
아마도 get , post 와 같이 데이터를 주고받는 건 익숙했지만 바꾸는 건 처음이어서 헤맸던 것 같았습니다...
하지만 결국 해결했습니다~!~!~!

useEffect(() => {
    if (winner.length === 1) {
      toggleModal();
      axios
        .put(`${BASE_URL}/world/${keyword}`, {
          ...data,
          list: data.list,
          count: (data.count += 1),
        })
        .then((res) => console.log('성공'));
    }
  }, [winner]);

코드처럼 기존의 데이터를 스프레드 문법으로 이용하며 수정할 값의 데이터를 입력하여 객체 형식의 데이터로 만든 후 put 메서드를 사용해서 기존의 데이터를 수정했습니다.


netlify 배포 관련 에러들

배포 후 새로고침 했을 때 의 에러

해당 이미지 처럼 netlify로 배포를 했을 때 에러가 뜨는 현상이 있었습니다.
원인은 netlify에 Redirect설정을 하지 않았기 때문에 처음에 사이트를 어디로 Redirect시킬 것인지 모르기 때문입니다.
참고 자료 링크

배포 진행 중에 "build.command" failed 에러

해당 에러는 Build command 에 CI=npm run build 를 사용하여 해결 했습니다.
참고 자료 링크


프로젝트 후기

프론트엔드 개발자가 되겠다고 결심했을 때 나의 생각을 코드를 통해 하나의 웹사이트로 완성하고 싶었던 목표가 있었다

지금까지 내가 진행한 프로젝트는 물론 내가 생각한 걸 구현 하지 못했던 건 아니지만 위코드 부트캠프, 원티드 프리 온보딩 과정을 수료하는 과정의 일부로서 프로젝트를 진행했다

하지만 이번 프로젝트는 모든 과정을 수료하고 정말 그저 내가 평소에 갖고 있던 생각을 웹사이트로 만들고 싶어서 시작했다.
프론트엔드 공부를 시작하고 이번 월드컵 프로젝트를 통해 개발을 시작했을 때의 목표를 이룰 수 있었다.

이번 월드컵 프로젝트는 지금까지 해온 프로젝트 중 가장 완성도가 뛰어나다고 생각한다
특히 사용자가 사이트를 이용하는 흐름에 있어서 잘 짜여져있다고 생각한다
사용자가 월드컵을 참여하고
참여한 월드컵들로 쌓인 데이터를 통계로 나타내었고
그런 통계를 보며 사용자들끼리 의견을 나눌 수 있는 댓글 창을 만들었고
사용자가 월드컵을 생성하여 통계와 유저들의 의견을 접할 수 있게 개발했다.

이것을 가능하게 하기 위해서 서버가 필요했다
프론트엔드 개발자 두명 뿐이었지만 json-server 라이브러리를 통해 간단하게 필요한 REST-API를 구축했다.
낯설었기에 어려웠지만 소중한 경험이 된 것 같다..!

이제는 배포한 것 에서 끝나지 않고 사이트를 한번 운영해보려고 한다
지금으로서는 문제점이 없어 보이지만 유저들이 사용하면서 미처 개발 중에 생각하지 못했던 부분이 있다면 수정을 해서 사이트를 유지보수 해나가 볼 것이다

또 해보고 싶은 것이 있는데 월드컵 중에 라면 월드컵이 있다
통계가 어느 정도 쌓인다면 라면 월드컵의 통계와 시장의 라면 판매순위를 비교하여 우리 사이트가 얼마나 유의미한 통계를 내는지도 비교 해보고 싶다.

토이프로젝트를 며칠 동안 밤새워가면서 진행했지만 시간 가는 줄 몰랐고 정말 재밌게 한 것 같다
과정이 쉽지만은 않았지만 완성된 월드컵 프로젝트를 보기만 해도 너무 뿌듯하다!
얼마나 뿌듯하냐면 배포까지 끝낸 후 페이지를 두 세 시간 동안 만지작거렸던 것 같다...
내가 만들었지만 정말 너무나도 잘 만들었다는 생각에 잠겨 계속해서 만져보았다...
다음에도 좋은 아이디어가 떠오르면 또 토이프로젝트를 해보고 싶다!!

끝!

좋은 웹페이지 즐겨찾기