[20221014_함께하개 프로젝트]

YunTrollpark·2022년 10월 14일
3

Project_5(함께하개)

목록 보기
1/1

1. 프로젝트 하실래요!?

부트캠프 수료 후 취업스터디를 하면서 지내고 있었는데, 같은 기수분이 재미있는 아이디어가 있다면서 같이 프로젝트를 하자고 권유를 해주셔서 덕분에 기획 + 디자인 + 개발 + 배포까지 전부 해볼 수 있었다.
프론트엔드 5명, 백엔드 1명 총 6명이 참여했다.
내가 맡은 페이지는 검사 페이지와 검사 결과 페이지를 담당했고, 검사 결과를 구하는 알고리즘은 프론트에서 계산을 했다.

2. 기획


본격적으로 시작하기에 앞서 각자의 기획안이나 아이디어를 노션에 정리했다. 나는 강아지 MBTI라는 아이디어가 괜찮아서 그걸 기반으로 초기 기획안을 구성했다.

2-1) 내가 생각한 초기 기획안

• 기존 MBTI의 경우 질문지를 읽었을때 한번에 이해하기 힘든 항목들이 많았음 → 특히 주인 본인이 아닌 '반려견'의 행동을 바탕으로 하기 때문에 질문지가 한번에 이해가도록 하기
• 강아지 최종 결과가 나오면 보통 성향 + 어떤 놀이를 좋아한다 이런 예시가 간단히 나오는데 여기서 더 나아가서 우리는 그 결과를 바탕으로 장난감, 놀이방법, 장소 등 추천을 링크를 걸어서 연결
• 나온 결과를 바탕으로 성향이 잘 맞는 친구 추천 → 그걸 활용한 커뮤니티를 만들어서 타 반려견 서비스랑 차이점을 둠

2-2) 회의를 거친 최종 기획안


그리고 팀원분들과 회의를 거쳐서 최종 기획안 + userflow가 나오게 됐다!

2-3) 주요 서비스

• 로그인 및 회원가입: 일반 로그인, 카카오로그인, 구글 로그인
• MBTI 검사 페이지: 총 20문항으로 이루어져 있으며, 진행상황을 알려주는 막대기가 있음
• MBTI 결과 페이지: 16개의 유형 중 검사 페이지에서 나온 검사 결과를 바탕으로 나타내줌. 4가지 검사비율을 그래프로 나타내줌. sns 공유 가능
• 게시판: 로그인한 유저만 사용가능, 사진과 글을 작성 할 수 있으며 댓글기능도 있음. 부적절한 게시글의 경우 게시글 신고 가능
• 채팅방: 로그인한 유저만 사용가능, 궁합이 잘 맞는 MBTI 끼리 묶어져 있음. 로그인한 상태로 검사하고 결과페이지에서 채팅방 추천을 누르면 맞는 채팅방 추천 해줌.
• 관리자 페이지: 부적절한 게시글을 지울 수 있으며, 사용자도 탈퇴가능

2-4) 사용한 기술 스택

내가 사용한 기술 스택은 react, styled-component, javascript, typescript, redux 등이 있다.

3. 시연 영상

1) 비로그인시 MBTI검사

2) 로그인시 MBTI검사

4. MBTI 검사 페이지

1) MBTI 검사 결과 도출하는 코드

이번에 프로젝트 인원 구성이 백엔드 1명, 프론트엔드 5명이라서 백엔드에서 MBTI 검사결과까지 계산하는게 힘들것 같아서 이번에 프론트에서 계산하는 코드를 구현하기로 했다.

  const numberArr = value => {
    const newArr = [0, 0, 0, 0, 0]; // 1️⃣
    for (let i = 0; i < value.length; i++) { // 2️⃣
      if (value[i] === '매우 아니다') {
        newArr[i] = -2;
      } else if (value[i] === '아니다') {
        newArr[i] = -1;
      } else if (value[i] === '그렇다') {
        newArr[i] = 1;
      } else if (value[i] === '매우 그렇다') {
        newArr[i] = 2;
      }
    }
    const sumResult = newArr.reduce((a, b) => a + b); // 3️⃣
    if (sumResult === 0) {
      return newArr[0]; // 4️⃣
    } else {
      return sumResult;
    }
  };

1️⃣ 초기값 문제 때문에 배열에 숫자를 넣었음. 우리 문제지는 MBTI 한 항목당 5개의 정답지가 전부라서 숫자 5개를 넣었음.

2️⃣ for 반복문을 사용해서 각 정답을 숫자로 환산함

3️⃣ 환산하면 배열에 총 5개의 숫자가 들어가는데 이걸 전부 더해줌

4️⃣ 질문지를 작성 하다 보니 총합 '0'점이 나오는 경우가 있었다. 그래서 팀원들과 상의하여 0점이 나올경우 1번 질문지로 돌아가서 그 질문지의 점수를 최종 점수로 return하게 만듬(그래서 각 MBTI 질문지의 1번 질문지는 각항목에서 가장 메인되는 질문을 넣음)

  const setEnergy = (energyAnswerResult): void => {
    const energyScore = numberArr(energyAnswerResult); // 1️⃣
    if (energyScore < 0) { // 2️⃣
      setMBTIResult.push({
        id: 0,
        mbti: 'I',
        score: energyScore,
        layout: 'lefttop',
      });
    } else if (energyScore > 0) { // 3️⃣
      setMBTIResult.push({
        id: 0,
        mbti: 'E',
        score: energyScore,
        layout: 'lefttop',
      });
    }
  };

1️⃣ 사용자가 선택한 답이 배열로 들어옴 → 해당 배열을 위에 선택한 답안을 점수로 환산하는 함수에 넣어서 계산

2️⃣ 해당 점수의 합이 음수면 'I'

3️⃣ 해당 점수의 합이 양수면 'E'

// 질문의 정답지 onClick에 들어가는 함수
  const handleSetEnergyName = (value, id) => {
    const newArr = [0]; // 1️⃣
    for (let i = 0; i < energyNameList.length; i++) { // 2️⃣
      if (energyNameList[i].testId === id) {
        energyNameList[i].answerValue = value;
      } else {
        newArr[0]++;
      }
    }
    if (newArr[0] === energyNameList.length) { // 3️⃣
      energyNameList.push({ testId: id, answerValue: value });
    }
    setEnergyNameList(energyNameList); 
  };

1️⃣ 마찬가지로 초기값 문제 때문에 배열에 값을 하나 지정해줌

2️⃣ for 반복문을 돌아서 만약 1번 문제에 답을 체크한 상태에서 다시 1번 문제에서 다른 답을 클릭하면, 배열에 추가하는게 아니라 답을 변경하라는 코드

3️⃣ 만약에 정답이 2개인 상태에서 다른 문제의 답을 클릭하면 해당 testId와 선택한 답 answerValue를 배열에 추가하는 코드

이 코드가 내 프로젝트의 핵심코드인데... 사용자가 정답을 체크할때의 여러가지 상황을 감안했어야 했다.
고려한 사항으로는
1) 사용자가 질문지를 위에서 차례로 클릭하지 않을 수 있음
2) 사용자가 1번 질문에 '아니다'를 선택하고 다시 '매우 아니다'등의 다른 답안을 선택할 수 있음

그래서 최종적으로 위의 코드가 나옴!
여기서 testId는 문제의 정답지를 나타낸다! 그래서 useState에 하단처럼 저장이됨!
[
{testId: 0, answerValue: '그렇다'},
{testId: 1, answerValue: '매우 아니다'},
{testId: 2, answerValue: '매우 아니다'},
{testId: 3, answerValue: '매우 그렇다'},
{testId: 4, answerValue: '그렇다'}
]

2) ProgressBar 코드

import styled from 'styled-components/macro';

const ProgressBar = ({ percentLength }: { percentLength: number }) => { // 1️⃣
  return (
    <ProgressBarContainer>
      <ProgressBarWrapper>
        <ProgressBox>
          <ProgressPosition percentLength={percentLength} />
        </ProgressBox>
      </ProgressBarWrapper>
    </ProgressBarContainer>
  );
};
const ProgressBarContainer = styled.div`
  ${props => props.theme.flex.flexBox('row', 'center', 'center')}
  position: fixed;
  top: 4.813rem;
  width: 100%;
  height: 4.688rem;
  background-color: #edeef0;
`;

const ProgressBarWrapper = styled.div`
  ${props => props.theme.flex.flexBox('row', 'center', 'center')}
  width: 75rem;
  height: 3.125rem;
  margin: 1rem 0 1.25rem 0;
  background-color: white;
  border-radius: 3.125rem;
`;

const ProgressBox = styled.div`
  width: 73.75rem;
  height: 1.875rem;
  border-radius: 1.875rem;
`;

const ProgressPosition = styled.div<{ percentLength: number }>`
  width: ${({ percentLength }) => percentLength * 5}%;  // 2️⃣
  height: 1.875rem;
  margin-right: auto;
  background-image: linear-gradient(to right, #8fefe6, #c3c8f3);
  border-radius: 1.875rem;
  transition: width 0.5s ease-in-out;
`;

export default ProgressBar;

1️⃣ MBTI 컴포넌트에서 useState에 담기는 배열의 길이를 props로 내려줌

2️⃣ props로 받은 값을 문제지는 총 20개 라서 progressBar 길이를 100이라고 했을때, 100 / 20 = 5
그래서 막대의 길이를 props로 받은 길이 X 5 해서 나타냄!
간단한 코드 + styled-component를 활용하여 라이브러리 없이 구현했다.

3) 로그인한 사용자의 경우 MBTI값 변경

  const sendMbti = new FormData();
  const [cookies] = useCookies(['userToken']); 
  const mbtiResultText: SendMBTI = useSelector(
    (state: RootState) => state.mbtiText
  ); // 1️⃣
  const getMBTIResult: string = Object.values(mbtiResultText).toString(); // 2️⃣
  sendMbti.append('mbti', getMBTIResult); // 3️⃣
  const getUserId = useSelector((state: RootState) => state.user.userData.id); // 4️⃣
  const sendMBTI = () => {
    if (cookies.userToken && true) {
      axios.patch(`${BASE_URL}/users/${getUserId}`, sendMbti, { // 5️⃣
        headers: {
          Authorization: `Bearer ${cookies.userToken}`,
        },
      });
    }
  };

1️⃣ redux에 저장된 mbti 결과 값을 가지고옴

2️⃣ key, value로 이루어진 mbti 결과를 문자열로 변경

3️⃣ 'mbti'라는 key값에 있는 value를 getMBTIResult값으로 변경

4️⃣ redux에 있는 해당 user의 id값을 가져옴

5️⃣ 만약에 userToken값이 일치하면 백엔드에 해당 user의 userId값을 찾아 mbti 결과값을 변경함(headers에는 해당 user의 token값을 넣어서 보냄)

4) MBTI 검사한 사용자수

  fetch(`${BASE_URL}/test-count`, {
      method: 'POST',
    });

fetch로 백엔드에 method는 'POST'로 '결과보러가기'버튼이 눌려지면 count +1 요청하는 코드

5. MBTI 결과 페이지

1) mock data에서 맞는 결과 찾아오기

뮨제의 redux... MBTI검사 컴포넌트랑 MBTI 결과 보여주는 컴포넌트는 아예 분리 된 컴포넌트라서... 이제 더이상 미룰수없다... redux... 처음에는 너무 이해가 안가서 팀원분들이랑 같이 강의 실시간으로 보면서 코드 따라쳐보고 집가서 종이에 쓰면서 이해를 했다!

  const getMBTIResult: string = Object.values(mbtiResultText).toString(); // 1️⃣ 
  const resultMBTI = MBTI_RESULT.filter(item => { // 2️⃣
    return item.MBTI === getMBTIResult;
  });

1️⃣ redux로 가져온 값을 받으면 {mbti: 'ENTP'}이런식으로 결과가 옴. 그래서 value만 문자열로 변경해주는 코드. {mbti: 'ENTP'} → 'ENTP'

2️⃣ 문자열로 변경된 mbti를 활용해서 MBTI_RESULTmock-data에서 filter를 돌아서 목데이터의 MBTI = 문자열로 변경된 결과값이면 해당 mock-data를 반환함

2) MBTI 결과 그래프

 const titleProps = (mbti: string) => { // 1️⃣
    if (mbti === 'E' || mbti === 'I') {
      return '활동성';
    } else if (mbti === 'S' || mbti === 'N') {
      return '관계성';
    } else if (mbti === 'T' || mbti === 'F') {
      return '반응';
    } else if (mbti === 'P' || mbti === 'C') {
      return '판단';
    }
  };

  const propsMBTI = (mbti: string) => { // 2️⃣
    if (mbti === 'E') {
      return '#E4A6AB';
    } else if (mbti === 'I') {
      return '#1D4260';
    } else if (mbti === 'S') {
      return '#FCD148';
    } else if (mbti === 'N') {
      return '#99BABB';
    } else if (mbti === 'T') {
      return '#FF89AE';
    } else if (mbti === 'F') {
      return '#96AAE0';
    } else if (mbti === 'P') {
      return '#7EA296';
    } else if (mbti === 'C') {
      return '#67CBB3';
    }
  };

  const leftScore = (score: number) => { // 3️⃣
    if (score > 0) {
      return 5 * (10 + score);
    } else if (score < 0) {
      return 5 * (10 - score);
    }
  };

1️⃣ 결과 페이지에서는 총 4개의 그래프가 나옴. 이 하나의 컴포넌트로 4개의 그래프 전부 사용하고 싶어서 redux에서 가져온 값을 통하여 해당 그래프가 어떤걸 나타내는지 알려줌

2️⃣ 그래프의 색을 보여주기 위한 함수. 저렇게 문자에 따라 반환하는 색을 styled-component를 활용하여 다르게 표현해줌

3️⃣ 앞에서 봤듯이 점수는 음수나 양수가 나옴 → 그래프로 보여주려면 양수로 보여줘야함 → 그래서 점수가 양수면 그냥 곱해주고, 음수면 양수로 변환해서 곱해줌!

3) 채팅방 추천

 const mbtiResultText = useSelector((state: RootState) => state.mbtiText);  // 1️⃣
  const getMBTIResult: string = Object.values(mbtiResultText).toString(); // 2️⃣
  const [isShowModal, setIsShowModal] = useState(false); // 3️⃣
  const [roomID, setRoomID] = useState<number>();

  const [cookies] = useCookies(['userToken']);
  const checkLogin = cookies.userToken && true; // 4️⃣

  const resultModal = CHATLIST_DATA.filter(item => {
    return item.id === roomID;
  });

  const handleClick = (getMBTIResult: string) => { // 5️⃣
    if (room1.includes(getMBTIResult)) {
      setRoomID(1);
    } else if (room2.includes(getMBTIResult)) {
      setRoomID(2);
    } else if (room3.includes(getMBTIResult)) {
      setRoomID(3);
    } else if (room4.includes(getMBTIResult)) {
      setRoomID(4);
    }
  };

  const onClickToggleModal = () => { // 6️⃣
    setIsShowModal(!isShowModal);
  };

1️⃣ useSelector를 활용해서 redux에 저장된 mbti 결과 값을 가져옴

2️⃣ 가져온 결과값은 {mbti: 'ENTP'} 형식으로 가져와서 value만 문자로 변경

3️⃣ 채팅방 입장이 Mdoal로 나타나서 modal의 기본 상태값을 나타내는 useState

4️⃣ 채팅방의 경우 로그인한 사용자만 이용가능해서 cookie에 token이 있는지 없는지로 로그인 상태 확인

5️⃣ 이 부분이 좀 힘들었는데 filter가 아닌 includes를 이용한 이유가 있다!
filter의 경우 filter된 값을 반환하지만 includes는 true or false만 반환해줌.
내 코드의 경우 해당 mbti가 있다면 setRoomID(1) 실행 해주면 되기 때문에 includes를 사용했음!
그래서 해당 mbti가 있다면 setRoomID에 방번호를 넣어줘서 해당 방으로 이동할 수 있게 함!

6️⃣ 채팅방 추천 버튼을 누르면 Modal 상태를 false에서 true로 변경.

4) MBTI 검사한 사용자 수

  const [count, setCount] = useState<CountUser>({ userNum: 0 });

  const getNum = () => {
    fetch(`${BASE_URL}/test-count`, { // 1️⃣
      method: 'GET',
    })
      .then(response => response.json())
      .then(result => {
        setCount(result);
      });
  };

  useEffect(() => {
    getNum();
  }, []);

  const userCount = Object.values(count)
    .toString()
    .replace(/\B(?=(\d{3})+(?!\d))/g, ','); // 2️⃣

1️⃣ 백엔드에서 지정해준 url에 'GET'메서드로 count된 user의 값을 가져옴

2️⃣ user의 검사수를 문자열로 바꾸고, 세자리 마다 ','를 찍으라는 정규식

6. 프로젝트 후기

프로젝트 하면서 일이 정말 많았는데... 개인적으로 내가 일이 크게 터져서 프로젝트를 포기하려고 했었는데, 팀원분들이 기다려주셔서 유종의 미를 거두었다ㅠㅜ 너무 감사하다... 그래서 맡은 부분은 최선을 다해서 구현하려고 했고 sns공유까지 구현을 했다! 마지막 배포가 되고 사람들의 반응을 보았는데 다들 재미있어해서 너무 뿌듯했다! 6명이서 분업을 해서 기획부터 디자인, 자료조사, 개발까지! 그리고 관리자페이지까지 구성해서 정말 완성도 높은 프로젝트가 탄생했다! 앞으로도 개발자하면서 힘들때 프로젝트 했던 기억들이 나를 다시 일어나게 할 수 있을것 같다. 같이 고생해준 팀원들에게 다시 한번 박수를 보낸다!

7. 브랜딩






사이트 주소!

http://withdog.me/

profile
코딩으로 세상에 이야기하는 개발자

1개의 댓글

comment-user-thumbnail
2022년 10월 14일

와!! 아이디어가 너무 좋은 프로젝트에요! :) 저희 고양이도 한번 해봐야겠어요!

답글 달기