Skip to content

Gilpop8663/project_world_cup

Repository files navigation

배포 링크

프로젝트 소개

월드컵 프로젝트 소개

월드컵 사이트는 하나의 주제에 16개의 항목이 존재하고 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 값으로 저장했습니다.

헤더

header gif

  • google 로그인에서 받은 정보를 담은 state 값을 이용해서 로그인,비로그인 상태에 따라 다르게 화면에 표시되도록 하였습니다.
  • 모바일 화면에서는 메뉴들을 한번에 담기에 화면이 작아서 메뉴를 눌러 모달이 열리는 형식으로 고안하게 되었습니다.

로딩 스피너(ActivityIndicator)

스피너 gif

  • 로딩 중 사용자 경험을 좋게 개선하기 위해 로딩 스피너가 화면에 표시되도록 하였습니다.

About

월드컵 토이 프로젝트입니다.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published